[OpenMap Users] Changes in OpenMap 4.6.2

From: Piotr Kamiński <Piotr.Kaminski_at_ctm.gdynia.pl>
Date: Tue, 25 Jan 2005 17:31:04 +0100

Hi Don,

A few days ago I update our source code tree to OpenMap 4.6.2. During
this process I found a few files (with our changes), which I had not
sent you yet. Here is a list:

    * |LayerPane| - changed the way on/off button works - now you can
      select it via properties
    * |LayerControllButtonPanel| - localization
    * |LayersPanel| - properties
    * |NavigatePanel| - localization
    * |ProjectionStackTool| - I18N
    * |com/bbn/openmap/gui/I18N_pl (gui_I18N_pl) |
    * |com/bbn/openmap/gui/menu/I18N_pl ||(gui_menu_I18N_pl) |
    * |com/bbn/openmap/omGraphics/I18N_pl (omGraphics_I18N_pl)|
    * |Quantize| - long type was used instead of int - we found this
      bug while working with big pictures
    * |AbstractPlugIn| - removeable property (I've just found mistake in
      this property name - it should be 'removable' I think - the same
      bug is in Layer class, please correct this)
    * |Layer| - removeConfirmed methods added
    * |ProjMath| - there was problem with calculation made with float type
    * |MGRSPoint| - endless loop problem
    * |GraticuleLayer| - boxy changed to 'protected' (our subclass needs
      this), additional parameter in properties

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.

 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.

3. In LatLonPoint class equals method is not "synchronized" with
hashCode method. It is recommended that hashCode and equals are
symmetric methods -> if x.equals(y) then x.hashCode(x) must ==
y.hashCode(). I think that current implementation not supports this. .I
wander if using LatLonPoint as a key in HashMap or Set would lead to
some "strange" problems.

Waiting to hear from you.,
Best regards,
Piotr Kaminski


// **********************************************************************
//
// <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/gui/LayerPane.java,v $
// $RCSfile: LayerPane.java,v $
// $Revision: 1.2 $
// $Date: 2003/10/10 15:43:21 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.gui;

import java.awt.*;
import java.awt.event.*;
import java.io.Serializable;
import java.net.URL;

import javax.swing.*;

import com.bbn.openmap.*;
import com.bbn.openmap.util.Debug;

/**
 * A LayerPane is a single instance of how a layer represented in the
 * LayersPanel. It contains three widgets: an on/off button; a palette
 * button; and a toggle button with the layer name.
 * <P>
 */
public class LayerPane extends JPanel implements Serializable, ActionListener,
        ComponentListener {

    protected transient boolean paletteButtonAsCheckBox = false;
    protected transient JToggleButton onoffButton;
    // check box behavior of palette button
    protected transient JToggleButton paletteCheckBox;
    // simple button behavior of palette button
    protected transient JButton paletteButton;
    protected transient JToggleButton layerName;
    protected transient boolean selected;
    protected transient Layer layer;
    
    protected transient LayerHandler layerHandler;

    // the icons
    protected static transient URL url1;
    protected static transient ImageIcon paletteIcon;
    protected static transient URL url2;
    protected static transient ImageIcon paletteOnIcon;
    protected static transient URL url3;
    protected static transient ImageIcon layerOnIcon;
    protected static transient URL url4;
    protected static transient ImageIcon layerOffIcon;

    public transient final static String showPaletteCmd = "showPalette";
    public transient final static String toggleLayerCmd = "toggleLayerCmd";

    protected Color offColor;
    protected Color onColor = new Color(0xFF0066CC);

    // default initializations
    static {
        url1 = LayerPane.class.getResource("PaletteOff.gif");
        paletteIcon = new ImageIcon(url1, "palette");
        url2 = LayerPane.class.getResource("PaletteOn.gif");
        paletteOnIcon = new ImageIcon(url2, "palette on");

        url3 = LayerPane.class.getResource("BulbOn.gif");
        layerOnIcon = new ImageIcon(url3, "layer selected");
        url4 = LayerPane.class.getResource("BulbOff.gif");
        layerOffIcon = new ImageIcon(url4, "layer not selected");
    }

    /**
     * _at_param layer the layer to be represented by the pane.
     * _at_param bg the buttongroup for the layer
     * _at_param layerHandler the LayerHandler that contains information
     * about the Layers.
     */
    public LayerPane(Layer layer, LayerHandler layerHandler, ButtonGroup bg) {
        this(layer, layerHandler, bg, false);
    }
    
   /**
     * _at_param layer the layer to be represented by the pane.
     * _at_param bg the buttongroup for the layer
     * _at_param layerHandler the LayerHandler that contains information
     * about the Layers.
     * _at_param paletteButtonAsCheckBox true if palette button should behave like CheckBox, false - regular button.
     * The default value is false.
     */
    public LayerPane(Layer layer, LayerHandler layerHandler, ButtonGroup bg, boolean paletteButtonAsCheckBox) {
        super();
        this.paletteButtonAsCheckBox = paletteButtonAsCheckBox;
        this.layer = layer;
        setLayerHandler(layerHandler);
        createGUI(bg);
        layer.addComponentListener(this);
    }

    protected void createGUI(ButtonGroup bg) {
        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();
        setLayout(gridbag);

        //onoffButton = new JCheckBox();
        onoffButton = new JToggleButton(layerOnIcon); //było Off
        onoffButton.setMargin(new Insets(0,0,0,0));
        
        onoffButton.setSelectedIcon(layerOnIcon);
        onoffButton.setActionCommand(toggleLayerCmd);
        onoffButton.addActionListener(this);
        onoffButton.setToolTipText("Turn " + layer.getName() + " layer on/off");

        // Determine if this layer has already been activated
        onoffButton.setSelected(layer.isVisible());

        // add the palette show/hide checkbutton
        if (this.paletteButtonAsCheckBox) {
            createPaletteCheckBox();
        } else {
            createPaletteButton();
        }
        
        layerName = new JToggleButton(layer.getName());
        layerName.setBorderPainted(false);
        layerName.addActionListener(this);
        offColor = layerName.getBackground();
        layerName.setToolTipText("Click to select layer");
        layerName.setHorizontalAlignment(SwingConstants.LEFT);
          bg.add(layerName);

        c.gridy = 0;
        c.gridx = GridBagConstraints.RELATIVE;
        c.anchor = GridBagConstraints.WEST;
        gridbag.setConstraints(onoffButton, c);
        add(onoffButton);
        
        if (this.paletteButtonAsCheckBox) {
            gridbag.setConstraints(paletteCheckBox, c);
            add(paletteCheckBox);
        } else {
            gridbag.setConstraints(paletteButton, c);
            add(paletteButton);
        }
        
        c.weightx = 1;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.insets = new Insets(1, 2, 1, 15);
        gridbag.setConstraints(layerName, c);
        add(layerName);
    }

    protected void createPaletteCheckBox() {
        paletteCheckBox = new JToggleButton(paletteOnIcon);
        paletteCheckBox.setMargin(new Insets(0,0,0,0));
        //paletteCheckBox = new JCheckBox();
        //paletteCheckBox = new JCheckBox(paletteIcon);
        paletteCheckBox.setSelected(false);
        //paletteCheckBox.setSelectedIcon(paletteOnIcon);
        if (layer.getGUI() == null) {
            paletteCheckBox.setEnabled(false);
            paletteCheckBox.setToolTipText("No tools available for " + layer.getName() + " layer");
        } else {
            paletteCheckBox.setToolTipText("Display/Hide tools for " + layer.getName() + " layer");
        }
        paletteCheckBox.setActionCommand(showPaletteCmd);
        paletteCheckBox.addActionListener(this);
    }
    
    protected void createPaletteButton() {
        paletteButton = new JButton(paletteIcon);
        paletteButton.setBorderPainted(false);
        if (layer.getGUI() == null) {
            paletteButton.setEnabled(false);
            paletteButton.setToolTipText("No tools available for " + layer.getName() + " layer");
        } else {
            paletteButton.setToolTipText("Display tools for " + layer.getName() + " layer");
        }
        paletteButton.setActionCommand(showPaletteCmd);
        paletteButton.addActionListener(this);
    }
    
    /**
     * Used for the background LayerPanel marker.
     */
    protected LayerPane(String title) {
        super();
        // prevent null pointers somewhere...
        this.layer = com.bbn.openmap.layer.SinkLayer.getSharedInstance();
        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();
        setLayout(gridbag);

        JSeparator sep = new JSeparator();
        sep.setToolTipText(title);

        c.anchor = GridBagConstraints.WEST;
        c.weightx = 1;
        c.fill = GridBagConstraints.HORIZONTAL;
        gridbag.setConstraints(sep, c);
        add(sep);
    }
  
    public void setLayerHandler(LayerHandler in_layerHandler) {
        layerHandler = in_layerHandler;
    }

    /**
     * _at_return LayerHandler if it has been found in the MapHandler or
     * set.
     */
    protected LayerHandler getLayerHandler() {
        return layerHandler;
    }

    /**
     * Same as cleanup, except the layer name toggle button gets
     * removed from the given button group.
     */
    public void cleanup(ButtonGroup bg) {
        if (bg != null) {
            bg.remove(layerName);
        }
        cleanup();
    }

    /**
     * LayerPane disconnects from listeners, nulls out components.
     */
    public void cleanup() {
        if (layer != null) {
            this.layer.removeComponentListener(this);
        }
        if (onoffButton != null) {
            onoffButton.removeActionListener(this);
        }
        if (paletteButton != null) {
            paletteButton.removeActionListener(this);
        }
        if (paletteCheckBox != null) {
            paletteCheckBox.removeActionListener(this);
        }
        layerName = null;
        this.layer = null;
        this.layerHandler = null;
        this.removeAll();
    }

    public Dimension getPreferredSize() {
        return new Dimension(200, 32);
    }

    public Dimension getMinimumSize() {
        return new Dimension(100, 20);
    }

    /**
     * _at_return whether the layer is on
     */
    public boolean isLayerOn() {
        return onoffButton.isSelected();
    }

    /**
     * Turns the button on or off
     */
    public void setLayerOn(boolean value) {
        onoffButton.setSelected(value);
    }

    /**
     * _at_return whether the palette for this layer is on
     */
    public boolean isPaletteOn() {
        return this.paletteButtonAsCheckBox ? paletteCheckBox.isSelected() : paletteButton.isSelected();
    }

    /**
     * Turns the palette button on or off
     */
    public void setPaletteOn(boolean value) {
 
         if (this.paletteButtonAsCheckBox) {
            paletteCheckBox.setSelected(value);
        } else {
            paletteButton.setSelected(value);
        }
    }

    /**
     * _at_return the status of the layerName toggle button
     */
    public boolean isSelected() {
        return layerName.isSelected();
    }

    /**
     * Highlights/unhighlights the layerName toggle button
     */
    public void setSelected(boolean select) {
        layerName.setSelected(select);

        String command = select ? LayersPanel.LayerSelectedCmd
                : LayersPanel.LayerDeselectedCmd;

        if (Debug.debugging("layercontrol")) {
            Debug.output("LayerPane for " + getLayer().getName() + " "
                    + command + ", firing event");
        }

        firePropertyChange(command, null, getLayer());
    }
    
    /**
     * _at_return the layer represented by this LayerPane
     */
    public Layer getLayer() {
        return layer;
    }

    public void finalize() {
        if (Debug.debugging("gc")) {
            Debug.output("LayerPane getting GC'd");
        }
    }

    /**
     * Tell the pane to check with the layer to get the current layer
     * name for it's label.
     */
    public void updateLayerLabel() {
        layerName.setText(getLayer().getName());
    }

    protected void showPalette() {
        layer.showPalette();
    }
    
    
    protected void hidePalette() {
        layer.hidePalette();
    }
    
    /**
     * ActionListener interface.
     * _at_param e ActionEvent
     */
    public void actionPerformed(java.awt.event.ActionEvent e) {

        if (e.getSource().equals(paletteButton)){
            setSelected(true);
            paletteButton.setIcon(paletteOnIcon);
            showPalette();

        } else if (e.getSource().equals(paletteCheckBox)){
            if (paletteCheckBox.isSelected()) {
                System.out.println("SELECTED");
                showPalette();
            } else {
                System.out.println("NOT SELECTED");
                hidePalette();
            }

        } else if (e.getSource().equals(onoffButton)) {
            setSelected(true);
            // layer is selected, add it to or remove it from map
            if (layerHandler != null) {
                Debug.message("layerspanel","LayerPane|actionPerformed calling layerHandler.turnLayerOn()");
                layerHandler.turnLayerOn(onoffButton.isSelected(), layer);
            }

            if (Debug.debugging("layerspanel")){
                Debug.output("LayerPane: Layer " + layer.getName() +
                             (layer.isVisible()?" is visible.":" is NOT visible"));
            }
        } else if (e.getSource().equals(layerName)) {
            setSelected(true);
        }
    }

    /**
     * Invoked when component has been resized.
     */
    public void componentResized(ComponentEvent e) {}

    /**
     * Invoked when component has been moved.
     */
    public void componentMoved(ComponentEvent e) {}

    /**
     * Invoked when component has been shown.
     */
    public void componentShown(ComponentEvent e) {
        if (Debug.debugging("layerspanel")){
            Debug.output("LayerPane: layer pane for " + layer.getName() +
                         " receiving componentShown event");
        }

        Component comp = e.getComponent();
        if (comp == null) {
        } else if (comp == layer){
            if (isLayerOn() != true) {
                setLayerOn(true);
                if (Debug.debugging("layerspanel")){
                    Debug.output("LayerPane: layer " + layer.getName() +
                                 " is now visible.");
                }
            }
        } else if (comp == layer.getPalette()) {
            if (this.paletteButtonAsCheckBox) {
                System.out.println("COMP SHOWN");
                paletteCheckBox.setSelected(true);
            } else {
                paletteButton.setIcon(paletteOnIcon);
            }
        }
    }

    /**
     * Invoked when component has been hidden.
     */
    public void componentHidden(ComponentEvent e) {
        if (Debug.debugging("layerspanel")){
            Debug.output("LayerPane: layer pane for " + layer.getName() +
                         " receiving componentHidden event");
        }
        Component comp = e.getComponent();

        if (comp == layer) {
            if (isLayerOn() != false){
                setLayerOn(false);
                if (Debug.debugging("layerspanel")){
                    Debug.output("LayerPane: layer " + layer.getName() +
                                 " is now hidden.");
                }
            }
        } else if (comp == layer.getPalette()) {
            if (this.paletteButtonAsCheckBox) {
            paletteCheckBox.setSelected(false);
            } else {
                paletteButton.setIcon(paletteIcon);
            }
        } else if (comp == null) {
            if (Debug.debugging("layerspanel")){
                Debug.output("LayerPane: layer " + layer.getName() +
                             " is now hidden.");
            }
        }
    }

    protected static LayerPane backgroundLayerSeparator;

    public static LayerPane getBackgroundLayerSeparator(String title) {
        if (backgroundLayerSeparator == null) {
            backgroundLayerSeparator = new LayerPane(title);
        }
        return backgroundLayerSeparator;
    }
}


// **********************************************************************
//
// <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/LayerControlButtonPanel.java,v $
// $RCSfile: LayerControlButtonPanel.java,v $
// $Revision: 1.6 $
// $Date: 2004/10/14 18:18:16 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.gui;

import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.util.Properties;
import javax.swing.*;

import com.bbn.openmap.Layer;
import com.bbn.openmap.layer.util.LayerUtils;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;
import com.bbn.openmap.util.propertyEditor.*;

/**
 * A OMComponentPanel that provides controls to manupulate layer order
 * in the LayersPanel, and to provide add layer and delete layer
 * buttons. This panel can be embedded into the LayersPanel, or it can
 * be positioned somewhere else in the application. The LayersPanel
 * can be set to create one of these itself, in which case it will be
 * embedded. If you don't want an embedded version, create one and add
 * it to the MapHandler, it will hook up to the LayersPanel when it
 * finds it. The LayerPanes, LayersPanel and LayerControlButtonPanel
 * communicate with each other using PropertyChangeEvents. The
 * LayerPanes notify the LayersPanelwhen one of them is selected, and
 * that event gets passed to this panel. When a button on this panel
 * is pressed, it fires a PropertyChangeEvent with the layer and
 * command to take to all its PropertyChangeListeners.
 * <P>
 *
 * The LayerControlButtonPanel takes these properties:
 *
 * <pre>
 *
 *
 *
 * # Direction buttons are laid out, vertical or horizontal (vertical is default).
 * orientation=vertical
 * # Flag on whether to insert buttons onto LayersPanel (true by default).
 * embedded=true
 * # Configuration setting when embedding into LayersPanel (WEST,
 * # NORTH, EAST, SOUTH, NORTH_SOUTH) NORTH_SOUTH puts up button above
 * # list, down button below list.
 * configuration=WEST
 * # Flag to put button that lets the user delete layers (true by default).
 * delete=true
 * # Flag to put button that lets the user add layers, if the
 * # LayersAddPanel is discovered in the MapHandler (true by default)
 * add=true
 *
 *
 *
 * </pre>
 */
public class LayerControlButtonPanel extends OMComponentPanel implements
        ActionListener, PropertyChangeListener {

    // Images
    protected static transient URL urlup;
    protected static transient ImageIcon upgif;
    protected static transient URL urlupc;
    protected static transient ImageIcon upclickedgif;
    protected static transient URL urltop;
    protected static transient ImageIcon topgif;
    protected static transient URL urltopc;
    protected static transient ImageIcon topclickedgif;
    protected static transient URL urldown;
    protected static transient ImageIcon downgif;
    protected static transient URL urldownc;
    protected static transient ImageIcon downclickedgif;
    protected static transient URL urlbottom;
    protected static transient ImageIcon bottomgif;
    protected static transient URL urlbottomc;
    protected static transient ImageIcon bottomclickedgif;
    protected static transient URL urldelete;
    protected static transient ImageIcon deletegif;
    protected static transient URL urldeletec;
    protected static transient ImageIcon deleteclickedgif;
    protected static transient URL urladd;
    protected static transient ImageIcon addgif;
    protected static transient URL urladdc;
    protected static transient ImageIcon addclickedgif;

    /**
     * Static default initializations.
     */
    static {
        urlup = LayersPanel.class.getResource("Up.gif");
        upgif = new ImageIcon(urlup, "Up");

        urlupc = LayersPanel.class.getResource("Up.gif");
        upclickedgif = new ImageIcon(urlupc, "Up (clicked)");

        urltop = LayersPanel.class.getResource("DoubleUp.gif");
        topgif = new ImageIcon(urltop, "Top");

        urltopc = LayersPanel.class.getResource("DoubleUp.gif");
        topclickedgif = new ImageIcon(urltopc, "Top (clicked)");

        urldown = LayersPanel.class.getResource("Down.gif");
        downgif = new ImageIcon(urldown, "Down");

        urldownc = LayersPanel.class.getResource("Down.gif");
        downclickedgif = new ImageIcon(urldownc, "Down (clicked)");

        urlbottom = LayersPanel.class.getResource("DoubleDown.gif");
        bottomgif = new ImageIcon(urlbottom, "Bottom");

        urlbottomc = LayersPanel.class.getResource("DoubleDown.gif");
        bottomclickedgif = new ImageIcon(urlbottomc, "Bottom (clicked)");

        urldelete = LayersPanel.class.getResource("DeleteLayer.gif");
        deletegif = new ImageIcon(urldelete, "Delete");

        urldeletec = LayersPanel.class.getResource("DeleteLayer.gif");
        deleteclickedgif = new ImageIcon(urldeletec, "Delete (clicked)");

        urladd = LayersPanel.class.getResource("AddLayer.gif");
        addgif = new ImageIcon(urladd, "Add");

        urladdc = LayersPanel.class.getResource("AddLayer.gif");
        addclickedgif = new ImageIcon(urladdc, "Add (clicked)");
    }
 
    protected JButton add = null;
    protected JButton delete = null;
    protected JButton top = null;
    protected JButton up = null;
    protected JButton down = null;
    protected JButton bottom = null;

    protected LayerAddPanel layerAddPanel;

    public final static String OrientationProperty = "orientation";
    public final static String ConfigurationProperty = "configuration";
    public final static String EmbeddedProperty = "embedded";
    public final static String DeleteLayersProperty = "delete";
    public final static String AddLayersProperty = "add";

    public final static String HORIZONTAL_CONFIG = OrientationPropertyEditor.HORIZONTAL;
    public final static String VERTICAL_CONFIG = OrientationPropertyEditor.VERTICAL;

    public final static String WEST_CONFIG = "WEST";
    public final static String EAST_CONFIG = "EAST";
    public final static String NORTH_CONFIG = "NORTH";
    public final static String SOUTH_CONFIG = "SOUTH";
    public final static String NORTH_SOUTH_CONFIG = "NORTH_SOUTH";
    public final static String DefaultConfiguration = WEST_CONFIG;

    protected int orientation = BoxLayout.Y_AXIS; // BoxLayout.X_AXIS
    protected String configuration = DefaultConfiguration;
    protected boolean embedded = true;
    protected boolean deleteLayers = true;
    protected boolean addLayers = true;

    public LayerControlButtonPanel() {
        super();
        createInterface();
    }

    public LayerControlButtonPanel(LayersPanel panel) {
        this();
        setLayersPanel(panel);
    }

    public void removeLayersPanel(LayersPanel panel) {
        if (panel != null) {
            panel.setControls(null);
            panel.removePropertyChangeListener(this);
            removePropertyChangeListener(panel);

            if (embedded) {
                if (configuration.equalsIgnoreCase(NORTH_SOUTH_CONFIG)) {
                    panel.remove(up);
                    panel.remove(down);
                } else {
                    panel.remove(this);
                }
            }
        }
    }

    /**
     * Sets this panel to control the LayersPanel. If you want to
     * extend this class and change how the buttons are displayed in
     * the LayersPanel, change this method.
     */
    public void setLayersPanel(LayersPanel panel) {
        if (panel != null) {
            // Just in case it's already been added.
            panel.removePropertyChangeListener(this);

            panel.addPropertyChangeListener(this);
            addPropertyChangeListener(panel);

            if (embedded) {
                createInterface(); // again, reset for new config
                // values

            setLayout(new BoxLayout(this, orientation));

            if (panel.getLayout() instanceof BorderLayout) {
                if (configuration.equalsIgnoreCase(WEST_CONFIG)) {
                    panel.add(this, BorderLayout.WEST);
                } else if (configuration.equalsIgnoreCase(EAST_CONFIG)) {
                    panel.add(this, BorderLayout.EAST);
                } else if (configuration.equalsIgnoreCase(NORTH_CONFIG)) {
                    panel.add(this, BorderLayout.NORTH);
                } else if (configuration.equalsIgnoreCase(SOUTH_CONFIG)) {
                    panel.add(this, BorderLayout.SOUTH);
                } else if (configuration.equalsIgnoreCase(NORTH_SOUTH_CONFIG)) {
                    panel.add(up, BorderLayout.NORTH);
                    panel.add(down, BorderLayout.SOUTH);
                }
            } else {
                panel.add(this);
            }
        }
            // Let the LayersPanel know who is controlling it.
            panel.setControls(this);
        }
    }

    protected void createInterface() {
        removeAll();

          setAlignmentX(LEFT_ALIGNMENT);
          setAlignmentY(CENTER_ALIGNMENT);
          setLayout(new BoxLayout(this, orientation));

        top = new JButton(topgif);
        top.setActionCommand(LayersPanel.LayerTopCmd);
        top.setPressedIcon(topclickedgif);
        top.setToolTipText(i18n.get(LayerControlButtonPanel.class,"moveLayerToTop","Move selected layer to top"));
        top.addActionListener(this);
          add(top);

        up = new JButton(upgif);
        up.setActionCommand(LayersPanel.LayerUpCmd);
        up.setPressedIcon(upclickedgif);
        up.setToolTipText(i18n.get(LayerControlButtonPanel.class,"moveLayerUpOne","Move selected layer up one"));
        up.addActionListener(this);
          add(up);

        down = new JButton(downgif);
        down.setPressedIcon(downclickedgif);
        down.setActionCommand(LayersPanel.LayerDownCmd);
        down.setToolTipText(i18n.get(LayerControlButtonPanel.class,"moveLayerDownOne","Move selected layer down one"));
        down.addActionListener(this);
          add(down);

        bottom = new JButton(bottomgif);
        bottom.setPressedIcon(bottomclickedgif);
        bottom.setActionCommand(LayersPanel.LayerBottomCmd);
        bottom.setToolTipText(i18n.get(LayerControlButtonPanel.class,"moveLayerToBottom","Move selected layer to bottom"));
        bottom.addActionListener(this);
          add(bottom);

        if (deleteLayers) {
            JLabel blank = new JLabel(" ");
            add(blank);
            
            delete = new JButton(deletegif);
            delete.setActionCommand(LayersPanel.LayerRemoveCmd);
            delete.setToolTipText(i18n.get(LayerControlButtonPanel.class,"removeLayer","Remove selected layer"));
            delete.addActionListener(this);
            delete.setEnabled(false);
            add(delete);
        }

        if (addLayers && add != null) {
            add(add);
        }
    }

    /**
     * Set the panel that brings up an interface to dynamically add
     * layers.
     */
    public void setLayerAddPanel(LayerAddPanel lap) {
        layerAddPanel = lap;

        if (layerAddPanel != null) {
            add = new JButton(addgif);
            add.setActionCommand(LayersPanel.LayerAddCmd);
            add.setToolTipText(i18n.get(LayerControlButtonPanel.class,"addLayer","Add a layer"));
            add.addActionListener(this);
            if (addLayers) {
                this.add(add);
            }
        } else if (add != null) {
            this.remove(add);
        }

    }

    /**
     * Get the panel interface to dynamically add layers.
     */
    public LayerAddPanel getLayerAddPanel() {
        return layerAddPanel;
    }

    /**
     * Method associated with the ActionListener interface. This
     * method listens for action events meant to change the order of
     * the layers, as fired by the layer order buttons.
     *
     * _at_param e ActionEvent
     */
    public void actionPerformed(java.awt.event.ActionEvent e) {

        String command = e.getActionCommand();

        if (Debug.debugging("layerbuttons")) {
            Debug.output("LayersPanel.actionPerformed(): " + command);
        }

        if (command == LayersPanel.LayerTopCmd
                || command == LayersPanel.LayerBottomCmd
                || command == LayersPanel.LayerUpCmd
                || command == LayersPanel.LayerDownCmd
                || command == LayersPanel.LayerRemoveCmd) {
            if (selected != null) {
                if (Debug.debugging("layercontrol")) {
                    Debug.output("LayerControlButtonPanel: button firing "
                            + command + " event for " + selected.getName());
                }
                firePropertyChange(command, null, selected);
            } else {
                if (Debug.debugging("layercontrol")) {
                    Debug.output("LayerControlButtonPanel: button firing "
                            + command + " event with no layer selected");
                }
            }
        } else if (command.equals(LayersPanel.LayerAddCmd)) {
            if (layerAddPanel != null) {
                layerAddPanel.showPanel();
            }
        }
    }

    protected Layer selected = null;

    public void propertyChange(PropertyChangeEvent pce) {
        String command = pce.getPropertyName();
        Object obj = pce.getNewValue();
        if (Debug.debugging("layercontrol")) {
            Debug.output("LayerControlButtonPanel: receiving PropertyChangeEvent "
                    + pce.getPropertyName());
        }

        if (command == LayersPanel.LayerSelectedCmd && obj instanceof Layer) {

            selected = (Layer) obj;

            delete.setEnabled(selected.isRemoveable());

            if (Debug.debugging("layercontrol")) {
                Debug.output("LayerControlButtonPanel: got notification that layer is selected: "
                        + selected.getName());
            }
        } else if (command == LayersPanel.LayerDeselectedCmd && selected == obj) {
            selected = null;
            delete.setEnabled(false);
        }
    }

    public void findAndInit(Object someObj) {
        if (someObj instanceof LayerAddPanel) {
            setLayerAddPanel((LayerAddPanel) someObj);
        }

        if (someObj instanceof LayersPanel) {
            setLayersPanel((LayersPanel) someObj);
        }
    }

    public void findAndUndo(Object someObj) {
        if (someObj instanceof LayerAddPanel) {
            if (getLayerAddPanel() == someObj) {
                setLayerAddPanel(null);
            }
        }

        if (someObj instanceof LayersPanel) {
            removeLayersPanel((LayersPanel) someObj);
        }
    }

    public void setProperties(String prefix, Properties props) {
        super.setProperties(prefix, props);
        prefix = PropUtils.getScopedPropertyPrefix(prefix);

        configuration = props.getProperty(prefix + ConfigurationProperty);

        if (configuration == null) {
            configuration = DefaultConfiguration;
        }

        embedded = LayerUtils.booleanFromProperties(props, prefix
                + EmbeddedProperty, embedded);
        deleteLayers = LayerUtils.booleanFromProperties(props, prefix
                + DeleteLayersProperty, deleteLayers);
        addLayers = LayerUtils.booleanFromProperties(props, prefix
                + AddLayersProperty, addLayers);

        String orient = props.getProperty(prefix + OrientationProperty);
        if (orient != null
                && (orient.equalsIgnoreCase(HORIZONTAL_CONFIG) || (orient.equalsIgnoreCase("false")))) {
            orientation = BoxLayout.X_AXIS;
        }
    }

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

        props.put(prefix + ConfigurationProperty, configuration);
        props.put(prefix + OrientationProperty,
                (orientation == BoxLayout.X_AXIS ? HORIZONTAL_CONFIG
                        : VERTICAL_CONFIG));
        props.put(prefix + EmbeddedProperty, new Boolean(embedded).toString());
        props.put(prefix + DeleteLayersProperty,
                new Boolean(deleteLayers).toString());
        props.put(prefix + AddLayersProperty, new Boolean(addLayers).toString());
        return props;
    }

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

        props.put(ConfigurationProperty,
                "Pre-Defined Configuration String (WEST, EAST, NORTH, SOUTH, NORTH_SOUTH).");
        props.put(OrientationProperty, "Horizontal or Vertical.");
        props.put(OrientationProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.OrientationPropertyEditor");
        props.put(EmbeddedProperty, "Insert itself into LayersPanel.");
        props.put(EmbeddedProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
        props.put(DeleteLayersProperty, "Include button to delete layers.");
        props.put(DeleteLayersProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
        props.put(AddLayersProperty, "Include button to add layers.");
        props.put(AddLayersProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");

        return props;
    }

}

// **********************************************************************
//
// <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/gui/LayersPanel.java,v $
// $RCSfile: LayersPanel.java,v $
// $Revision: 1.2 $
// $Date: 2003/10/03 00:47:11 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.gui;

import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Properties;

import javax.swing.*;

import com.bbn.openmap.*;
import com.bbn.openmap.layer.util.LayerUtils;
import com.bbn.openmap.event.LayerEvent;
import com.bbn.openmap.event.LayerListener;
import com.bbn.openmap.util.ComponentFactory;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;

/**
 * The LayersPanel displays the list of layers that OpenMap can
 * display. The layer name is displayed accompanied by an on/off
 * button and a tool palette button. Pressing the on/off button will
 * cause the the map to display/remove the layer. Pressing the tool
 * palette button will cause a window to be displayed containing
 * widgets specific to that layer.
 * <p>
 *
 * The order of the layers in the list reflects the order that the
 * layers are displayed on the map, with the bottom-most layer listed
 * on the panel underneath all the the other layers displayed on the
 * map. The order of the layers is determined by their order in the
 * Layer[] passed in the setLayers method.
 * <p>
 *
 * The order of the layers can be changed by sending the LayersPanel
 * an ActionEvent with one of the string commands in the class, or by
 * sending a PropertyChangeEvent with a command and a Layer as the new
 * value.
 * <P>
 *
 * In the standard GUI, the order can be changed by selecting a layer
 * by clicking on the layer's name (or on either of buttons), then
 * clicking on one of the four buttons on the left side of the panel.
 * The four buttons signify, from top to bottom: Move the selected
 * layer to the top; Move the selected layer up one position; Move the
 * selected layer down one position; Move the selected layer to the
 * bottom.
 * <P>
 *
 * The LayersPanel can be used within a BeanContext. If it is added to
 * a BeanConext, it will look for a LayerHandler to add itself to as a
 * LayerListener. The LayersPanel can only listen to one LayerHandler,
 * so if more than one is found, only the last one found will be used.
 * If another LayerHandler is added to the BeanContext later, the new
 * LayerHandler will be used. The LayersPanel is also considered to be
 * a Tool, which will cause a button that will bring up the
 * LayersPanel to be automatically added to the ToolPanel if a
 * ToolPanel is part of the BeanContext.
 * <P>
 *
 * When the LayersPanel discovers a BufferedLayerMapBean is being
 * used, it adds a special LayerPane to its LayerPane list that shows
 * which layers are being buffered in the MapBean. This special
 * LayerPane shows up as a line in the list, and all layers below that
 * line are being specially buffered by the BufferedLayerMapBean.
 * <P>
 *
 * The properties that can be set for the LayersPanel:
 *
 * <pre>
 *
 *
 *
 * # Use LayerStatusPanes for the layers if true, otherwise
 * # LayerPanes. LayerStatusPanes turn the on/off bulbs to green/red
 * # bulbs when the layer is resting/working. LayerPanes just show
 * # yellow bulbs when the layer is part of the map.
 * showStatus=true
 * # Specify the behavior of palette button. If false (default)
 * # the button behaves as a simple button. If true it behaves as
 * # a check box.
 * paletteButtonAsCheckBox=true
 * # When the BufferedLayerMapBean is used, a divider will be
 * # displayed in the list of layers showing which layers are in the
 * # MapBean buffer (below the line). Commands to move layers, by
 * # default, respect this divider, requiring more commands to have
 * # layers cross it.
 * boundary=true
 * # Add control buttons - use &quot;none&quot; for no button. If undefined,
 * # the LayerControlButtonPanel will be created automatically.
 * controls=com.bbn.openmap.gui.LayerControlButtonPanel
 * # Any control properties added here, prepended by &quot;controls&quot;...
 * controls.configuration=WEST
 *
 *
 *
 * </pre>
 */
public class LayersPanel extends OMToolComponent implements Serializable,
        ActionListener, LayerListener, PropertyChangeListener {
    /** Action command for the layer order buttons. */
    public final static String LayerTopCmd = "LayerTopCmd";
    /** Action command for the layer order buttons. */
    public final static String LayerBottomCmd = "LayerBottomCmd";
    /** Action command for the layer order buttons. */
    public final static String LayerUpCmd = "LayerUpCmd";
    /** Action command for the layer order buttons. */
    public final static String LayerDownCmd = "LayerDownCmd";
    /** Action command removing a layer. */
    public final static String LayerRemoveCmd = "LayerRemoveCmd";
    /** Action command adding a layer. */
    public final static String LayerAddCmd = "LayerAddCmd";
    /** Action command for notification that a layer has been selected. */
    public final static String LayerSelectedCmd = "LayerSelected";
    /**
     * Action command for notification that a layer has been
     * deselected. Not so reliable. Usually a selection notification
     * means that others are deselected.
     */
    public final static String LayerDeselectedCmd = "LayerDeselected";

    /**
     * A property to set the class to create for layer order controls.
     * If undefined, a LayerControlButtonPanel in its default
     * configuration will be created. For no controls added, use
     * (none) for this property.
     */
    public final static String ControlButtonsProperty = "controls";
    /**
     * A property that can be used for controlling how the to top and
     * to bottom cammands will be interpreted when a
     * BufferedLayerMapBean is used. See the definition of
     * bufferedBoundary.
     */
    public final static String BufferedBoundaryProperty = "boundary";
    /**
     * A property that can be used for controlling what type of
     * LayerPanes are used. If true (default) a LayerStatusPane will
     * be created for each layer. Otherwise, a LayerPane will be used.
     */
    public final static String ShowStatusProperty = "showStatus";
   /**
     * A property that can be used for controlling behavior of
     * palette button.
     */
    public final static String PaletteButtonAsCheckBoxProperty = "PaletteButtonAsCheckBox";

    /**
     * A value for the (controls) property to not include control
     * buttons in the interface.
     */
    public final static String NO_CONTROLS = "none";

    /** Default key for the LayersPanel Tool. */
    public final static String defaultKey = "layerspanel";

    /**
     * The LayerHandler to listen to for LayerEvents, and also to
     * notify if the layer order should change.
     */
    protected transient LayerHandler layerHandler = null;
    /**
     * Panel that lets you dynamically add and configure layers.
     */
    protected transient LayerAddPanel layerAddPanel = null;
    /**
     * The components holding the layer name label, the on/off
     * indicator and on button, and the palette on/off indicator and
     * palette on button.
     */
    protected transient LinkedList panes;
    /** The internal component that holds the panes. */
    protected transient JPanel panesPanel;
    /** The scroll pane to use for panes. */
    protected transient JScrollPane scrollPane;
    /** The Layer order adjustment button group. */
    protected transient ButtonGroup bg;
    /** The ActionListener that will bring up the LayersPanel. */
    protected ActionListener actionListener;
    /**
     * The frame used when the LayersPanel is used in an application
     * and the actionListener is called.
     */
    protected transient JFrame layersWindowFrame;
    /**
     * The frame used when the LayersPanel is used in an applet and
     * the actionListener is called.
     */
    protected transient JInternalFrame layersWindow;
    /** The set of buttons that control the layers. */
    protected LayerControlButtonPanel controls = null;
    /**
     * Hashtable that tracks LayerPanes for layers, with the layer as
     * the key and LayerPane as the value.
     */
    protected Hashtable paneLookUp = new Hashtable();
    /**
     * A special LayerPane used when the LayersPanel senses that a
     * BufferedLayerMapBean is being used. This LayersPanel is a
     * separating line showing which layers are part of the MapBean's
     * buffer, and which are not.
     */
    protected LayerPane backgroundLayerSeparator = null;
    /**
     * Behavior flag so that if there is a background buffered layer
     * on the MapBean, and a buffered layer divider in the
     * LayersPanel, whether commands instructing a layer to the top or
     * bottom of the list should honor the virtual boundary between
     * buffered and unbuffered layers. That is, if a layer is on the
     * bottom of the buffered list and is instructed to go to the top
     * of the overal list, it will only first travel to the top of the
     * buffered layers. On a subsequent top command, it will go to the
     * top of the list. The same behavior applies for going down. True
     * is default. If set to false, these commands will just send the
     * selected layer to the top and bottom of the entire list.
     */
    protected boolean bufferedBoundary = true;

    /**
     * Behavior flag that determines what kind of LayerPane is used
     * for the layers. If true (default) the LayerStatusPane will be
     * used. Otherwise, the LayerPane will be used instead.
     */
    protected boolean showStatus = true;
    
    /**
     * Behavior flag that determines how palette button should work:
     * as a check box or as a simple button. If false (default)
     * it behaves as a simple button.
     */
    protected boolean paletteButtonAsCheckBox = false;
    /**
     * Construct the LayersPanel.
     */
    public LayersPanel() {
        super();
        setKey(defaultKey);
        setLayout(new BorderLayout());
        setWindowSupport(new WindowSupport(this, "Layers"));
    }

    /**
     * Construct the LayersPanel.
     *
     * _at_param lHandler the LayerHandler controlling the layers.
     */
    public LayersPanel(LayerHandler lHandler) {
        this();
        setLayerHandler(lHandler);
    }

    /**
     * Set the LayerHandler that the LayersPanel listens to. If the
     * LayerHandler passed in is not null, the LayersMenu will be
     * added to the LayerHandler LayerListener list, and the
     * LayersMenu will receive a LayerEvent with the current layers.
     * <P>
     *
     * If there is a LayerHandler that is already being listened to,
     * then the LayersPanel will remove itself from current
     * LayerHandler as a LayerListener, before adding itself to the
     * new LayerHandler.
     * <P>
     *
     * Lastly, if the LayerHandler passed in is null, the LayersPanel
     * will disconnect itself from any LayerHandler currently held,
     * and reset itself with no layers.
     *
     * _at_param lh LayerHandler to listen to, and to use to reorder the
     * layers.
     */
    public void setLayerHandler(LayerHandler lh) {
        if (layerHandler != null) {
            layerHandler.removeLayerListener(this);
        }
        layerHandler = lh;
        if (layerHandler != null) {
            layerHandler.addLayerListener(this);
        } else {
            setLayers(new Layer[0]);
        }
        updateLayerPanes(layerHandler);
    }

    /**
     * Get the LayerHandler that the LayersPanel listens to and uses
     * to reorder layers.
     *
     * _at_return LayerHandler.
     */
    public LayerHandler getLayerHandler() {
        return layerHandler;
    }

    /**
     * Set the layerpanes with the given layerhandler
     *
     * _at_param layerHandler The LayerHandler controlling the layers
     */
    protected void updateLayerPanes(LayerHandler layerHandler) {
        Iterator it = getPanes().iterator();
        while (it.hasNext()) {
            ((LayerPane) it.next()).setLayerHandler(layerHandler);
        }
    }

    /**
     * LayerListener interface method. A list of layers will be added,
     * removed, or replaced based on on the type of LayerEvent. The
     * LayersPanel only reacts to LayerEvent.ALL events, to reset the
     * components in the LayersPanel.
     *
     * _at_param evt a LayerEvent.
     */
    public void setLayers(LayerEvent evt) {
        Layer[] layers = evt.getLayers();
        int type = evt.getType();

        if (type == LayerEvent.ALL) {
            Debug.message("layerspanel", "LayersPanel received layers update");
            setLayers(layers);
        }
    }

    /**
     * Tool interface method. The retrieval tool's interface. This
     * method creates a button that will bring up the LayersPanel.
     *
     * _at_return String The key for this tool.
     */
    public Container getFace() {
        JButton layerButton = null;

        if (getUseAsTool()) {
            layerButton = new JButton(new ImageIcon(OMToolSet.class.getResource("layers.gif"), "Layer Controls"));
            layerButton.setBorderPainted(false);
            layerButton.setToolTipText("Layer Controls");
        layerButton.setMargin(new Insets(0, 0, 0, 0));
            layerButton.addActionListener(getActionListener());
        }

        return layerButton;
    }
    
    /**
     * Get the ActionListener that triggers the LayersPanel. Useful to
     * have to provide an alternative way to bring up the LayersPanel.
     *
     * _at_return ActionListener
     */
    public ActionListener getActionListener() {
        return new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    WindowSupport ws = getWindowSupport();
                    int w = 328;
                    int h = 300;
            
                    Dimension dim = ws.getComponentSize();
                    if (dim != null) {
                    w = (int) dim.getWidth();
                    h = (int) dim.getHeight();
                    }

                    int x = 10;
                    int y = 10;
            
                    Point loc = ws.getComponentLocation();
                    if (loc != null) {
                        x = (int) loc.getX();
                        y = (int) loc.getY();
                    }

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

                    ws.displayInWindow(frame, x, y, w, h);
                }
            };
    }

    /**
     * Set the layers that are in the LayersPanel. Make sure that the
     * layer[] is the same as that passed to any other OpenMap
     * component, like the LayersMenu. This method checks to see if
     * the layer[] has actually changed, in order or in size. If it
     * has, then createPanel() is called to rebuild the LayersPanel.
     *
     * _at_param inLayers the array of layers.
     */
    public void setLayers(Layer[] inLayers) {
        Layer[] layers = inLayers;

        if (inLayers == null) {
            layers = new Layer[0];
        }

        if (Debug.debugging("layerspanel")) {
            Debug.output("LayersPanel.setLayers() with " + layers.length
                    + " layers.");
        }

        LinkedList panes = getPanes();
        int separatorOffset = 0;
        if (backgroundLayerSeparator != null
                && panes.contains(backgroundLayerSeparator)) {
            separatorOffset = 1;
        }

        if (panes.size() - separatorOffset != layers.length) {
            // if the panel hasn't been created yet, or if someone has
            // changed the layers on us, rebuild the panel.
            createPanel(layers);
            return;
        }

        int i = 0;
        Iterator it = panes.iterator();
        while (it.hasNext() && i < layers.length) {
            LayerPane pane = (LayerPane) it.next();

            if (pane == backgroundLayerSeparator) {
                continue;
            }

            if (pane.getLayer() != layers[i]) {
                // If the layer order sways at all, then we start over
                // and rebuild the panel
                createPanel(layers);
                return;
            } else {
                pane.updateLayerLabel();
            }

            // Do this just in case someone has changed something
            // somewhere else...
            pane.setLayerOn(layers[i].isVisible());
            i++;
        }

        // One last check for a mismatch...
        if (it.hasNext() || i < layers.length) {
            createPanel(layers);
        }
        // If we get here, it means that what we had is what we
        // wanted.
    }
    
    protected LinkedList getPanes() {
        if (panes == null) {
            panes = new LinkedList();
        }
        return panes;
    }

    protected void setPanes(LinkedList lpa) {
        panes = lpa;
    }

    /**
     * Create the panel that shows the LayerPanes. This method creates
     * the on/off buttons, palette buttons, and layer labels, and adds
     * them to the scrollPane used to display all the layers.
     *
     * _at_param inLayers the Layer[] that reflects all possible layers
     * that can be added to the map.
     */
    public void createPanel(Layer[] inLayers) {
        Debug.message("layerspanel", "LayersPanel.createPanel()");

        if (scrollPane != null) {
            remove(scrollPane);
        }

        Layer[] layers = inLayers;
        if (layers == null) {
            layers = new Layer[0];
        }

        if (panesPanel == null) {
            panesPanel = new JPanel();
            panesPanel.setLayout(new BoxLayout(panesPanel, BoxLayout.Y_AXIS));
            panesPanel.setAlignmentX(LEFT_ALIGNMENT);
            panesPanel.setAlignmentY(BOTTOM_ALIGNMENT);
        } else {
            ((BoxLayout) panesPanel.getLayout()).invalidateLayout(panesPanel);
            panesPanel.removeAll();
        }

         if (bg == null) {
            bg = new ButtonGroup();
        }

        LinkedList panes = new LinkedList();
        LinkedList backgroundPanes = new LinkedList();

        // populate the arrays of CheckBoxes and strings used to fill
        // the JPanel for the panes
        for (int i = 0; i < layers.length; i++) {
            Layer layer = layers[i];
            if (layer == null) {
                Debug.output("LayersPanel caught null layer, " + i + " out of "
                        + layers.length);
                continue;
            }

            LayerPane lpane = (LayerPane) paneLookUp.get(layer);

            if (lpane == null) {
                if (Debug.debugging("layercontrol")) {
                    Debug.output("LayersPanel: Creating LayerPane for "
                            + layer.getName());
                }
                lpane = createLayerPaneForLayer(layer, layerHandler, bg);
                lpane.addPropertyChangeListener(LayerSelectedCmd, this);
                lpane.addPropertyChangeListener(LayerDeselectedCmd, this);
                paneLookUp.put(layer, lpane);
            } else {
                // In case this has been modified elsewhere...
                lpane.setLayerOn(layer.isVisible());
            }

            if (layer.getAddAsBackground() && backgroundLayerSeparator != null) {
                backgroundPanes.add(lpane);
            } else {
                panes.add(lpane);
                panesPanel.add(lpane);
            }
        }

        if (backgroundPanes.size() != 0) {
            if (Debug.debugging("layerspanel")) {
                Debug.output("Adding BackgroundLayerSeparator");
            }
            panes.add(backgroundLayerSeparator);
            panesPanel.add(backgroundLayerSeparator);
            panes.addAll(backgroundPanes);

            Iterator it = backgroundPanes.iterator();
            while (it.hasNext()) {
                panesPanel.add((LayerPane) it.next());
            }

        } else if (backgroundLayerSeparator != null) {
            if (Debug.debugging("layerspanel")) {
                Debug.output("No layers are background layers, adding separator");
            }
            panes.add(backgroundLayerSeparator);
            panesPanel.add(backgroundLayerSeparator);
        }

        setPanes(panes);

        if (scrollPane != null) {
            remove(scrollPane);
            scrollPane.removeAll();
            scrollPane = null;
        }

        scrollPane = new JScrollPane(panesPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        add(scrollPane, BorderLayout.CENTER);
        revalidate();
    }

    /**
     * Called when a new LayerPane needs to be created for a layer.
     * You can use this to extend LayerPane and return something else
     * that fits your GUI.
     */
    protected LayerPane createLayerPaneForLayer(Layer layer,
                                                LayerHandler layerHandler,
                                                ButtonGroup bg) {
        if (showStatus) {
            return new LayerStatusPane(layer, layerHandler, bg);
        } else {
            return new LayerPane(layer, layerHandler, bg);
        }
    }

    public void deletePanes(LinkedList dpanes) {
       Debug.message("layerspanel", "LayersPanel.deletePanes()");
        if (dpanes != null) {
            paneLookUp.clear();
            Iterator it = dpanes.iterator();
            while (it.hasNext()) {
                LayerPane pane = (LayerPane) it.next();
                if (pane != null && pane != backgroundLayerSeparator) {
                    pane.removePropertyChangeListener(this);
                    pane.cleanup(bg);
                }
            }
        }

        // Shouldn't call this, but it's the only thing
        // that seems to make it work...
        if (Debug.debugging("helpgc")) {
            System.gc();
        }
    }

    /**
     * Set up the buttons used to move layers up and down, or
     * add/remove layers. The button component should hook itself up
     * to the LayersPanel, and assume that the LayersPanel has a
     * BorderLayout with the list in the center spot.
     */
    protected void createControlButtons() {
        controls = new LayerControlButtonPanel(this);
    }

    /**
     * Set the LayerControlButtonPanel as the controls to this
     * LayersPanel. This method is just settng the controls variable
     * to point to the panel. No further hookups are made.
     *
     * _at_param lpb
     */
    public void setControls(LayerControlButtonPanel lpb) {
        controls = lpb;
    }

    public LayerControlButtonPanel getControls() {
        return controls;
    }

    /**
     * Method associated with the ActionListener interface. This
     * method listens for action events meant to change the order of
     * the layers, as fired by the layer order buttons.
     *
     * _at_param e ActionEvent
     */
    public void actionPerformed(java.awt.event.ActionEvent e) {
        String command = e.getActionCommand();
        if (Debug.debugging("layerspanel")) {
            Debug.output("LayersPanel.actionPerformed(): " + command);
        }

        try {
            LayerPane pane = findSelectedPane();
            if (pane != null) {
                moveLayer(pane, command);
            }
        } catch (NullPointerException npe) {
        } catch (ArrayIndexOutOfBoundsException aioobe) {
        }
    }

    /**
     * Change a layer's position.
     */
    public void moveLayer(Layer layer, String command) {
        if (Debug.debugging("layercontrol")) {
            Debug.output("LayersPanel.moveLayer(): " + command + " for "
                    + layer.getName());
        }

        moveLayer((LayerPane) paneLookUp.get(layer), command);
    }


    /**
     * Change a layer's position, with the layer represented by a
     * LayerPane.
     */
    protected void moveLayer(LayerPane lp, String command) {

        if (lp == null) {

            if (Debug.debugging("layercontrol")) {
                Debug.output("LayersPanel.moveLayer(): LayerPane not represented on list");
            }

            if (command == LayerRemoveCmd) {
                // OK, here's a hidden trick. If no layers are
                // selected
                // and the minus sign is clicked, then this is called.
                System.gc();
            }
            return;
        }

        LinkedList panes = getPanes();
        int row = panes.indexOf(lp);

        boolean boundary = false;
        int bls_row = -1;
        if (backgroundLayerSeparator != null) {
            bls_row = panes.indexOf(backgroundLayerSeparator);
            boundary = bufferedBoundary;
        }

        if (command.equals(LayerTopCmd)) {
            // Move layer selected layer to top
            panes.remove(lp);
            if (boundary && bls_row > 0 && row > bls_row + 1) {
                // If the backgroundLayerSeparator is more than one
                // above it, move to just below it on the first top
                // command.
                panes.add(bls_row + 1, lp);
            } else {
                panes.addFirst(lp);
            }

            rejiggerMapLayers();
        } else if (command.equals(LayerBottomCmd)) {
            // Move layer selected layer to bottom
            panes.remove(lp);

            if (boundary && bls_row > 0 && row < bls_row - 1) {
                // If the backgroundLayerSeparator is more than one
                // below it, move to just above it on the first top
                // command.
                panes.add(bls_row - 1, lp);
            } else {
                panes.addLast(lp);
            }

            rejiggerMapLayers();
        } else if (command.equals(LayerUpCmd)) {
            // Move layer selected layer up one
            if (row <= 0)
                return;
            panes.remove(row);
            panes.add(row - 1, lp);
            rejiggerMapLayers();
        } else if (command.equals(LayerDownCmd)) {
            // Move layer selected layer up one
            if (row < 0 || row == panes.size() - 1)
                return;
            panes.remove(row);
            panes.add(row + 1, lp);
            rejiggerMapLayers();
        } else if (command.equals(LayerRemoveCmd)) {

            if (layerHandler == null || !lp.getLayer().removeConfirmed()) {
                return;
            }

            // This order is somewhat important. lp.getLayer() will
            // be null after lp.cleanup. lp.setSelected() will cause
            // a series of property change notifications.
            lp.setSelected(false);
            lp.getLayer().setPaletteVisible(false);
            paneLookUp.remove(lp.getLayer());
            layerHandler.removeLayer(lp.getLayer());
            lp.cleanup(bg);

            // Shouldn't call this, but it's the only thing
            // that seems to make it work...
            if (Debug.debugging("helpgc")) {
                System.gc();
            }

            return;

        } else if (command.equals(LayerAddCmd)) {
            if (layerAddPanel != null) {
                layerAddPanel.showPanel();
            }
        }
    }

    /**
     * Find the selected LayerPane in the current LayerPane list. Will
     * return null if there isn't a selected pane.
     */
    protected LayerPane findSelectedPane() {
        Iterator it = getPanes().iterator();
        while (it.hasNext()) {
            LayerPane pane = (LayerPane) it.next();
            if (pane.isSelected()) {
                return pane;
            }
        }
        return null;
    }

   /**
     * Makes a new layer cake of active layers to send to
     * LayerHandler.setLayers().
     */
    protected void rejiggerMapLayers() {
        Debug.message("layerspanel", "LayersPanel.rejiggerMapLayers()");

        if (layerHandler == null) {
            // Why bother doing anything??
            return;
        }

        int selectedRow = -1;

        panesPanel.removeAll();

        LinkedList panes = getPanes();
        LinkedList layerList = new LinkedList();

        int bufferIndex = Integer.MAX_VALUE;

        int i = 0; // track layer index
        Iterator it = panes.iterator();
        while (it.hasNext()) {

            LayerPane pane = (LayerPane) it.next();

            if (pane == backgroundLayerSeparator) {
                panesPanel.add(backgroundLayerSeparator);
                bufferIndex = i++;
                continue;
            }

            Layer layer = pane.getLayer();
            layer.setAddAsBackground(i > bufferIndex);
            panesPanel.add(pane);
            layerList.add(layer);

            if (pane.isSelected()) {
                selectedRow = i;
            }
            i++;
        }

        scrollPane.revalidate();

        // Scroll up or down as necessary to keep selected row
        // viewable
        if (selectedRow >= 0) {
            int spheight = scrollPane.getHeight();
            JScrollBar sb = scrollPane.getVerticalScrollBar();
            int sv = sb.getValue();
            int paneheight = ((LayerPane) panes.get(selectedRow)).getHeight();
            int rowvalue = selectedRow * paneheight;
            // Don't reset scrollBar unless the selected row
            // is not in the viewable range
            if (!((rowvalue > sv) && (rowvalue < spheight + sv))) {
                sb.setValue(rowvalue);
            }
        }

        Object[] layerArray = layerList.toArray();
        int length = layerArray.length;
        Layer[] newLayers = new Layer[length];

        for (int j = 0; j < length; j++) {
            newLayers[j] = (Layer) layerArray[j];
        }

        layerHandler.setLayers(newLayers);
    }

    /**
     * Update the layer names - if a layer name has changed, tell the
     * LayerPanes to check with their layers to update their labels.
     */
    public synchronized void updateLayerLabels() {
        Iterator it = getPanes().iterator();
        while (it.hasNext()) {
            ((LayerPane) it.next()).updateLayerLabel();
        }
    }

    public void propertyChange(PropertyChangeEvent pce) {
        String command = pce.getPropertyName();
        Object obj = pce.getNewValue();

        if (Debug.debugging("layercontrol")) {
            Debug.output("LayersPanel receiving PropertyChangeEvent " + command
                    + ", " + pce.toString());
        }

        if ((command == LayerSelectedCmd || command == LayerDeselectedCmd)
                && obj instanceof Layer) {

            if (Debug.debugging("layercontrol")) {
                Debug.output("LayersPanel: layer panel notification that layer is selected: "
                        + ((Layer) obj).getName());
            }
            firePropertyChange(command, null, ((Layer) obj));

        } else if ((command == LayersPanel.LayerTopCmd
                || command == LayersPanel.LayerBottomCmd
                || command == LayersPanel.LayerUpCmd
                || command == LayersPanel.LayerDownCmd || command == LayersPanel.LayerRemoveCmd)
                && obj instanceof Layer) {
            if (Debug.debugging("layercontrol")) {
                Debug.output("LayersPanel: layer panel notification that layer should be raised: "
                        + ((Layer) obj).getName());
            }
            moveLayer((Layer) obj, command);
        }
    }

    /**
     * Called when the LayersPanel is added the BeanContext, or when
     * another object is added to the BeanContext after the
     * LayerHandler has been added. This allows the LayersPanel to
     * keep up-to-date with any objects that it may be interested in,
     * namely, the LayerHandler. If a LayerHandler has already been
     * added, the new LayerHandler will replace it.
     *
     * _at_param someObj the object being added to the BeanContext
     */
    public void findAndInit(Object someObj) {
        if (someObj instanceof LayerHandler) {
            // do the initializing that need to be done here
            Debug.message("layerspanel", "LayersPanel found a LayerHandler");
            setLayerHandler((LayerHandler) someObj);
        }

        if (someObj instanceof BufferedLayerMapBean) {
            if (Debug.debugging("layerspanel")) {
                Debug.output("LayersPanel found BufferedLayerMapBean, creating separator panel");
            }
            backgroundLayerSeparator = LayerPane.getBackgroundLayerSeparator(" --- Background Layers --- ");
        }

        // Don't want to forward ourselves on to controls, supposedly
        // they already know.
        if (controls instanceof LightMapHandlerChild && someObj != this) {
            ((LightMapHandlerChild) controls).findAndInit(someObj);
        }
    }

    /**
     * BeanContextMembershipListener method. Called when an object has
     * been removed from the parent BeanContext. If a LayerHandler is
     * removed, and it's the current one being listened to, then the
     * layers in the panel will be wiped clean.
     *
     * _at_param someObj the object being removed from the BeanContext
     */
    public void findAndUndo(Object someObj) {
        if (someObj instanceof LayerHandler) {
            // do the initializing that need to be done here
            Debug.message("layerspanel", "LayersPanel removing LayerHandler");
            if (getLayerHandler() == (LayerHandler) someObj) {
                setLayerHandler(null);
            }
        }

        // Don't want to forward ourselves on to controls, supposedly
        // they already know.
        if (controls instanceof LightMapHandlerChild && someObj != this) {
            ((LightMapHandlerChild) controls).findAndUndo(someObj);
        }
    }

    public void setProperties(String prefix, Properties props) {
        super.setProperties(prefix, props);
        prefix = PropUtils.getScopedPropertyPrefix(prefix);

        String controlString = props.getProperty(prefix
                + ControlButtonsProperty);

        if (controlString != NO_CONTROLS) {
            if (controlString == null) {
                createControlButtons();
            } else {
                Object obj = ComponentFactory.create(controlString, prefix
                        + ControlButtonsProperty, props);

                if (obj instanceof LayerControlButtonPanel) {
                    ((LayerControlButtonPanel) obj).setLayersPanel(this);
                }
            }
        }

        bufferedBoundary = LayerUtils.booleanFromProperties(props, prefix
                + BufferedBoundaryProperty, bufferedBoundary);
        showStatus = LayerUtils.booleanFromProperties(props, prefix
                + ShowStatusProperty, showStatus);
        paletteButtonAsCheckBox = LayerUtils.booleanFromProperties(props, prefix + PaletteButtonAsCheckBoxProperty, paletteButtonAsCheckBox);
    }

    public Properties getProperties(Properties props) {
        props = super.getProperties(props);

        String prefix = PropUtils.getScopedPropertyPrefix(this);
        LayerControlButtonPanel controls = getControls();
        if (controls != null) {
            props.put(prefix + ControlButtonsProperty, controls.getClass()
                    .getName());
            controls.getProperties(props);
        }
        props.put(prefix + BufferedBoundaryProperty,
                new Boolean(bufferedBoundary).toString());
        props.put(prefix + ShowStatusProperty,
                new Boolean(showStatus).toString());
        props.put(prefix + PaletteButtonAsCheckBoxProperty, new Boolean(paletteButtonAsCheckBox).toString());
        return props;
    }

    public Properties getPropertyInfo(Properties props) {
        props = super.getPropertyInfo(props);
        props.put(ControlButtonsProperty,
                "Class to use for layer control buttons (Optional)");
        LayerControlButtonPanel controls = getControls();
        if (controls != null) {
            controls.getPropertyInfo(props);
        }
        props.put(BufferedBoundaryProperty,
                "Force layer movement to respect background layer boundary.");
        props.put(BufferedBoundaryProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");

            props.put(ShowStatusProperty, "Use Layer Panes that show layer status.");
        props.put(ShowStatusProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
        props.put(PaletteButtonAsCheckBoxProperty, "If palette button should behave as a check box or as a simple button.");
        props.put(PaletteButtonAsCheckBoxProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
        return props;
    }
}



// **********************************************************************
//
// <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/gui/NavigatePanel.java,v $
// $RCSfile: NavigatePanel.java,v $
// $Revision: 1.4 $
// $Date: 2003/10/24 23:36:45 $
// $Author: dietrick $
//
// **********************************************************************


package com.bbn.openmap.gui;

import java.net.URL;
import java.io.Serializable;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import com.bbn.openmap.*;
import com.bbn.openmap.event.*;
import com.bbn.openmap.util.Debug;


/**
 * A Navigation Rosette Bean.
 * This bean is a source for PanEvents and CenterEvents.
 */
public class NavigatePanel extends OMToolComponent
    implements Serializable, ActionListener {

    public final static String panNWCmd = "panNW";
    public final static String panNCmd = "panN";
    public final static String panNECmd = "panNE";
    public final static String panECmd = "panE";
    public final static String panSECmd = "panSE";
    public final static String panSCmd = "panS";
    public final static String panSWCmd = "panSW";
    public final static String panWCmd = "panW";
    public final static String centerCmd = "center";

    protected transient JButton nwButton = null;
    protected transient JButton nButton = null;
    protected transient JButton neButton = null;
    protected transient JButton eButton = null;
    protected transient JButton seButton = null;
    protected transient JButton sButton = null;
    protected transient JButton swButton = null;
    protected transient JButton wButton = null;
    protected transient JButton cButton = null;

    // default icon names
    protected static String nwName = "nw.gif";
    protected static String nName = "n.gif";
    protected static String neName = "ne.gif";
    protected static String eName = "e.gif";
    protected static String seName = "se.gif";
    protected static String sName = "s.gif";
    protected static String swName = "sw.gif";
    protected static String wName = "w.gif";
    protected static String cName = "center.gif";

    protected PanSupport panDelegate;
    protected CenterSupport centerDelegate;
    protected boolean useTips = false;
    protected float panFactor = 1f;

// protected int height = 0; // calculated
// protected int width = 0; // calculated

    protected boolean useDefaultCenter = false;
    protected float defaultCenterLat = 0;
    protected float defaultCenterLon = 0;

    public final static String defaultKey = "navigatepanel";

    /**
     * Construct the NavigationPanel.
     */
    public NavigatePanel() {
        super();
        setKey(defaultKey);
        panDelegate = new PanSupport(this);
        centerDelegate = new CenterSupport(this);

        JPanel panel = new JPanel();
        GridBagLayout internalGridbag = new GridBagLayout();
        GridBagConstraints c2 = new GridBagConstraints();
        panel.setLayout(internalGridbag);

        // begin top row
        String info = i18n.get(NavigatePanel.class,"panel.northwest","Pan Northwest");
        nwButton = getButton(nwName, info, panNWCmd);
        c2.gridx = 0;
        c2.gridy = 0;
        internalGridbag.setConstraints(nwButton, c2);
        panel.add(nwButton);

        info = i18n.get(NavigatePanel.class,"panel.north","Pan North");
        nButton = getButton(nName, info, panNCmd);
        c2.gridx = 1;
        c2.gridy = 0;
        internalGridbag.setConstraints(nButton, c2);
        panel.add(nButton);

        info = i18n.get(NavigatePanel.class,"panel.northeast","Pan Northeast");
        neButton = getButton(neName, info, panNECmd);
        c2.gridx = 2;
        c2.gridy = 0;
        internalGridbag.setConstraints(neButton, c2);
        panel.add(neButton);

        // begin middle row
        info = i18n.get(NavigatePanel.class,"panel.west","Pan West");
        wButton = getButton(wName, info, panWCmd);
        c2.gridx = 0;
        c2.gridy = 1;
        internalGridbag.setConstraints(wButton, c2);
        panel.add(wButton);

        info = i18n.get(NavigatePanel.class,"panel.centerAtStart","Center Map at Starting Coords");
        cButton = getButton(cName, info, centerCmd);
        c2.gridx = 1;
        c2.gridy = 1;
        internalGridbag.setConstraints(cButton, c2);
        panel.add(cButton);

        info = i18n.get(NavigatePanel.class,"panel.east","Pan East");
        eButton = getButton(eName, info, panECmd);
        c2.gridx = 2;
        c2.gridy = 1;
        internalGridbag.setConstraints(eButton, c2);
        panel.add(eButton);

        // begin bottom row
        info = i18n.get(NavigatePanel.class,"panel.southwest","Pan Southwest");
        swButton = getButton(swName, info, panSWCmd);
        c2.gridx = 0;
        c2.gridy = 2;
        internalGridbag.setConstraints(swButton, c2);
        panel.add(swButton);

        info = i18n.get(NavigatePanel.class,"panel.south","Pan South");
        sButton = getButton(sName, info, panSCmd);
        c2.gridx = 1;
        c2.gridy = 2;
        internalGridbag.setConstraints(sButton, c2);
        panel.add(sButton);

        info = i18n.get(NavigatePanel.class,"panel.southeast","Pan Southeast");
        seButton = getButton(seName, info, panSECmd);
        c2.gridx = 2;
        c2.gridy = 2;
        internalGridbag.setConstraints(seButton, c2);
        panel.add(seButton);

        add(panel);
    }

    /**
     * Add the named button to the panel.
     *
     * _at_param name GIF image name
     * _at_param info ToolTip text
     * _at_param command String command name
     *
     */
    protected JButton getButton(String name, String info, String command) {
        URL url = NavigatePanel.class.getResource(name);
        ImageIcon icon = new ImageIcon(url, info);
        JButton b = new JButton(icon);
        b.setPreferredSize(new Dimension(icon.getIconWidth(),
                                         icon.getIconHeight()));
        b.setToolTipText(info);
        b.setMargin(new Insets(0,0,0,0));
        b.setActionCommand(command);
        b.addActionListener(this);
        b.setBorderPainted(Debug.debugging("layout"));
        b.setOpaque(false);
        return b;
    }

    /**
     * Add a CenterListener.
     * _at_param listener CenterListener
     */
    public synchronized void addCenterListener(CenterListener listener) {
        centerDelegate.addCenterListener(listener);
    }

    /**
     * Remove a CenterListener
     * _at_param listener CenterListener
     */
    public synchronized void removeCenterListener(CenterListener listener) {
        centerDelegate.removeCenterListener(listener);
    }

    /**
     * Add a PanListener.
     * _at_param listener PanListener
     */
    public synchronized void addPanListener(PanListener listener) {
        panDelegate.addPanListener(listener);
    }

    /**
     * Remove a PanListener
     * _at_param listener PanListener
     */
    public synchronized void removePanListener(PanListener listener) {
        panDelegate.removePanListener(listener);
    }

    /**
     * Fire a CenterEvent.
     */
    protected synchronized void fireCenterEvent(float lat, float lon) {
        centerDelegate.fireCenter(lat, lon);
    }

    /**
     * Fire a PanEvent.
     * _at_param az azimuth east of north
     */
    protected synchronized void firePanEvent(float az) {
        panDelegate.firePan(az);
    }

    /**
     * Get the pan factor.
     * <p>
     * The panFactor is the amount of screen to shift
     * when panning in a certain direction: 0=none, 1=half-screen shift.
     * _at_return float panFactor (0.0 &lt;= panFactor &lt;= 1.0)
     */
    public float getPanFactor() {
        return panFactor;
    }

    /**
     * Set the pan factor.
     * <p>
     * This defaults to 1.0. The panFactor is the amount of screen to shift
     * when panning in a certain direction: 0=none, 1=half-screen shift.
     * _at_param panFactor (0.0 &lt;= panFactor &lt;= 1.0)
     */
    public void setPanFactor(float panFactor) {
        if ((panFactor < 0f) || (panFactor > 1f)) {
            throw new IllegalArgumentException(
                    "should be: (0.0 <= panFactor <= 1.0)");
        }
        this.panFactor = panFactor;
    }

    /**
     * Use this function to set where you want the map projection to
     * pan to when the user clicks on "center" button on the
     * navigation panel. The scale does not change. When you call
     * this function, the projection does not change.
     * _at_param passedLat float the center latitude (in degrees)
     * _at_param passedLon float the center longitude (in degrees)
     */
    public void setDefaultCenter(float passedLat, float passedLon) {
        useDefaultCenter = true;
        defaultCenterLat = passedLat;
        defaultCenterLon = passedLon;
    }

    /**
     * ActionListener Interface.
     * _at_param e ActionEvent
     */
    public void actionPerformed(java.awt.event.ActionEvent e) {

        String command = e.getActionCommand();

        Debug.message("navpanel", "NavigatePanel.actionPerformed(): " +
                      command);
        if (command.equals(panNWCmd)) {
            firePanEvent(-45f);
        } else if (command.equals(panNCmd)) {
            firePanEvent(0f);
        } else if (command.equals(panNECmd)) {
            firePanEvent(45f);
        } else if (command.equals(panECmd)) {
            firePanEvent(90f);
        } else if (command.equals(panSECmd)) {
            firePanEvent(135f);
        } else if (command.equals(panSCmd)) {
            firePanEvent(180f);
        } else if (command.equals(panSWCmd)) {
            firePanEvent(-135f);
        } else if (command.equals(panWCmd)) {
            firePanEvent(-90f);
        } else if (command.equals(centerCmd)) {
            // go back to the center point

            float lat;
            float lon;
            if (useDefaultCenter) {
                lat = defaultCenterLat;
                lon = defaultCenterLon;
            } else {
                lat = Environment.getFloat(Environment.Latitude, 0f);
                lon = Environment.getFloat(Environment.Longitude, 0f);
            }
            fireCenterEvent(lat, lon);
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    //// OMComponentPanel methods to make the tool work with
    //// the MapHandler to find objects it needs.
    ///////////////////////////////////////////////////////////////////////////

    public void findAndInit(Object obj) {
        if (obj instanceof PanListener) {
            addPanListener((PanListener)obj);
        }
        if (obj instanceof CenterListener) {
            addCenterListener((CenterListener)obj);
        }
    }

    public void findAndUndo(Object obj) {
        if (obj instanceof PanListener) {
            removePanListener((PanListener)obj);
        }
        if (obj instanceof CenterListener) {
            removeCenterListener((CenterListener)obj);
        }
    }

}


// **********************************************************************
//
// <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/gui/LayersPanel.java,v $
// $RCSfile: LayersPanel.java,v $
// $Revision: 1.2 $
// $Date: 2003/10/03 00:47:11 $
// $Author: dietrick $
//
// **********************************************************************


package com.bbn.openmap.gui;

import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.beans.beancontext.*;
import java.io.Serializable;
import java.net.URL;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Properties;

import javax.swing.*;
import javax.swing.event.InternalFrameEvent;
import javax.accessibility.*;

import com.bbn.openmap.*;
import com.bbn.openmap.layer.util.LayerUtils;
import com.bbn.openmap.event.LayerEvent;
import com.bbn.openmap.event.LayerListener;
import com.bbn.openmap.event.LayerSupport;
import com.bbn.openmap.util.ComponentFactory;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;

/**
 * The LayersPanel displays the list of layers that OpenMap can display.
 * The layer name is displayed accompanied by an on/off button and a
 * tool palette button. Pressing the on/off button will cause the the
 * map to display/remove the layer. Pressing the tool palette button
 * will cause a window to be displayed containing widgets specific to
 * that layer. <p>
 *
 * The order of the layers in the list reflects the order that the
 * layers are displayed on the map, with the bottom-most layer listed
 * on the panel underneath all the the other layers displayed on the
 * map. The order of the layers is determined by their order in the
 * Layer[] passed in the setLayers method. <p>
 *
 * The order of the layers can be changed by sending the LayersPanel
 * an ActionEvent with one of the string commands in the class, or by
 * sending a PropertyChangeEvent with a command and a Layer as the new
 * value. <P>
 *
 * In the standard GUI, the order can be changed by selecting a layer
 * by clicking on the layer's name (or on either of buttons), then
 * clicking on one of the four buttons on the left side of the panel.
 * The four buttons signify, from top to bottom: Move the selected
 * layer to the top; Move the selected layer up one position; Move the
 * selected layer down one position; Move the selected layer to the
 * bottom. <P>
 *
 * The LayersPanel can be used within a BeanContext. If it is added
 * to a BeanConext, it will look for a LayerHandler to add itself to
 * as a LayerListener. The LayersPanel can only listen to one
 * LayerHandler, so if more than one is found, only the last one found
 * will be used. If another LayerHandler is added to the BeanContext
 * later, the new LayerHandler will be used. The LayersPanel is also
 * considered to be a Tool, which will cause a button that will bring
 * up the LayersPanel to be automatically added to the ToolPanel if a
 * ToolPanel is part of the BeanContext.<P>
 *
 * When the LayersPanel discovers a BufferedLayerMapBean is being
 * used, it adds a special LayerPane to its LayerPane list that shows
 * which layers are being buffered in the MapBean. This special
 * LayerPane shows up as a line in the list, and all layers below that
 * line are being specially buffered by the BufferedLayerMapBean. <P>
 *
 * The properties that can be set for the LayersPanel: <pre>
 * # Use LayerStatusPanes for the layers if true, otherwise
 * # LayerPanes. LayerStatusPanes turn the on/off bulbs to green/red
 * # bulbs when the layer is resting/working. LayerPanes just show
 * # yellow bulbs when the layer is part of the map.
 * showStatus=true
 * # Specify the behavior of palette button. If false (default)
 * # the button behaves as a simple button. If true it behaves as
 * # a check box.
 * paletteButtonAsCheckBox=true
 * # When the BufferedLayerMapBean is used, a divider will be
 * # displayed in the list of layers showing which layers are in the
 * # MapBean buffer (below the line). Commands to move layers, by
 * # default, respect this divider, requiring more commands to have
 * # layers cross it.
 * boundary=true
 * # Add control buttons - use "none" for no button. If undefined,
 * # the LayerControlButtonPanel will be created automatically.
 * controls=com.bbn.openmap.gui.LayerControlButtonPanel
 * # Any control properties added here, prepended by "controls"...
 * controls.configuration=WEST
 * </pre>
 */
public class LayersPanel extends OMToolComponent
    implements Serializable, ActionListener, LayerListener, PropertyChangeListener
{
    /** Action command for the layer order buttons. */
    public final static String LayerTopCmd = "LayerTopCmd";
    /** Action command for the layer order buttons. */
    public final static String LayerBottomCmd = "LayerBottomCmd";
    /** Action command for the layer order buttons. */
    public final static String LayerUpCmd = "LayerUpCmd";
    /** Action command for the layer order buttons. */
    public final static String LayerDownCmd = "LayerDownCmd";
    /** Action command removing a layer. */
    public final static String LayerRemoveCmd = "LayerRemoveCmd";
    /** Action command adding a layer. */
    public final static String LayerAddCmd = "LayerAddCmd";
    /** Action command for notification that a layer has been selected. */
    public final static String LayerSelectedCmd = "LayerSelected";
    /**
     * Action command for notification that a layer has been
     * deselected. Not so reliable. Usually a selection notification
     * means that others are deselected.
     */
    public final static String LayerDeselectedCmd = "LayerDeselected";

    /**
     * A property to set the class to create for layer order controls.
     * If undefined, a LayerControlButtonPanel in its default configuration
     * will be created. For no controls added, use (none) for this
     * property.
     */
    public final static String ControlButtonsProperty = "controls";
    /**
     * A property that can be used for controlling how the to top and
     * to bottom cammands will be interpreted when a
     * BufferedLayerMapBean is used. See the definition of
     * bufferedBoundary.
     */
    public final static String BufferedBoundaryProperty = "boundary";
    /**
     * A property that can be used for controlling what type of
     * LayerPanes are used. If true (default) a LayerStatusPane will
     * be created for each layer. Otherwise, a LayerPane will be
     * used.
     */
    public final static String ShowStatusProperty = "showStatus";
   /**
     * A property that can be used for controlling behavior of
     * palette button.
     */
    public final static String PaletteButtonAsCheckBoxProperty = "PaletteButtonAsCheckBox";

    /**
     * A value for the (controls) property to not include control
     * buttons in the interface.
     */
    public final static String NO_CONTROLS = "none";

    /** Default key for the LayersPanel Tool. */
    public final static String defaultKey = "layerspanel";

    /**
     * The LayerHandler to listen to for LayerEvents, and also to
     * notify if the layer order should change.
     */
    protected transient LayerHandler layerHandler = null;
    /**
     * Panel that lets you dynamically add and configure layers.
     */
    protected transient LayerAddPanel layerAddPanel = null;
    /**
     * The components holding the layer name label, the on/off
     * indicator and on button, and the palette on/off indicator and
     * palette on button.
     */
    protected transient LinkedList panes;
    /** The internal component that holds the panes. */
    protected transient JPanel panesPanel;
    /** The scroll pane to use for panes. */
    protected transient JScrollPane scrollPane;
    /** The Layer order adjustment button group. */
    protected transient ButtonGroup bg;
    /** The ActionListener that will bring up the LayersPanel. */
    protected ActionListener actionListener;
    /**
     * The frame used when the LayersPanel is used in an application
     * and the actionListener is called.
     */
    protected transient JFrame layersWindowFrame;
    /**
     * The frame used when the LayersPanel is used in an applet and
     * the actionListener is called.
     */
    protected transient JInternalFrame layersWindow;
    /** The set of buttons that control the layers. */
    protected LayerControlButtonPanel controls = null;
    /**
     * Hashtable that tracks LayerPanes for layers, with the layer as
     * the key and LayerPane as the value.
     */
    protected Hashtable paneLookUp = new Hashtable();
    /**
     * A special LayerPane used when the LayersPanel senses that a
     * BufferedLayerMapBean is being used. This LayersPanel is a
     * separating line showing which layers are part of the MapBean's
     * buffer, and which are not.
     */
    protected LayerPane backgroundLayerSeparator = null;
    /**
     * Behavior flag so that if there is a background buffered layer
     * on the MapBean, and a buffered layer divider in the
     * LayersPanel, whether commands instructing a layer to the top or
     * bottom of the list should honor the virtual boundary between
     * buffered and unbuffered layers. That is, if a layer is on the
     * bottom of the buffered list and is instructed to go to the top
     * of the overal list, it will only first travel to the top of the
     * buffered layers. On a subsequent top command, it will go to
     * the top of the list. The same behavior applies for going down.
     * True is default. If set to false, these commands will just
     * send the selected layer to the top and bottom of the entire
     * list.
     */
    protected boolean bufferedBoundary = true;

    /**
     * Behavior flag that determines what kind of LayerPane is used
     * for the layers. If true (default) the LayerStatusPane will be
     * used. Otherwise, the LayerPane will be used instead.
     */
    protected boolean showStatus = true;
    
    /**
     * Behavior flag that determines how palette button should work:
     * as a check box or as a simple button. If false (default)
     * it behaves as a simple button.
     */
    protected boolean paletteButtonAsCheckBox = false;
    /**
     * Construct the LayersPanel.
     */
    public LayersPanel() {
        super();
        setKey(defaultKey);
        setLayout(new BorderLayout());
        setWindowSupport(new WindowSupport(this, "Layers"));
    }

    /**
     * Construct the LayersPanel.
     *
     * _at_param lHandler the LayerHandler controlling the layers.
     */
    public LayersPanel(LayerHandler lHandler) {
        this();
        setLayerHandler(lHandler);
    }

    /**
     * Set the LayerHandler that the LayersPanel listens to. If the
     * LayerHandler passed in is not null, the LayersMenu will be
     * added to the LayerHandler LayerListener list, and the
     * LayersMenu will receive a LayerEvent with the current
     * layers. <P>
     *
     * If there is a LayerHandler that is already being listened to,
     * then the LayersPanel will remove itself from current LayerHandler
     * as a LayerListener, before adding itself to the new LayerHandler. <P>
     *
     * Lastly, if the LayerHandler passed in is null, the LayersPanel
     * will disconnect itself from any LayerHandler currently held,
     * and reset itself with no layers.
     *
     * _at_param lh LayerHandler to listen to, and to use to reorder the
     * layers.
     */
    public void setLayerHandler(LayerHandler lh) {
        if (layerHandler != null) {
            layerHandler.removeLayerListener(this);
        }
        layerHandler = lh;
        if (layerHandler != null) {
            layerHandler.addLayerListener(this);
        } else {
            setLayers(new Layer[0]);
        }
        updateLayerPanes(layerHandler);
    }

    /**
     * Get the LayerHandler that the LayersPanel listens to and uses
     * to reorder layers.
     * _at_return LayerHandler.
     */
    public LayerHandler getLayerHandler() {
        return layerHandler;
    }

    /**
     * Set the layerpanes with the given layerhandler
     * _at_param layerHandler The LayerHandler controlling the layers
     */
    protected void updateLayerPanes(LayerHandler layerHandler) {
        Iterator it = getPanes().iterator();
        while (it.hasNext()) {
            ((LayerPane)it.next()).setLayerHandler(layerHandler);
        }
    }

    /**
     * LayerListener interface method. A list of layers will be
     * added, removed, or replaced based on on the type of LayerEvent.
     * The LayersPanel only reacts to LayerEvent.ALL events, to reset
     * the components in the LayersPanel.
     *
     * _at_param evt a LayerEvent.
     */
    public void setLayers(LayerEvent evt) {
        Layer[] layers = evt.getLayers();
        int type = evt.getType();

        if (type==LayerEvent.ALL) {
            Debug.message("layerspanel", "LayersPanel received layers update");
            setLayers(layers);
        }
    }

    /**
     * Tool interface method. The retrieval tool's interface. This
     * method creates a button that will bring up the LayersPanel.
     *
     * _at_return String The key for this tool.
     */
    public Container getFace() {
        JButton layerButton = null;

        if (getUseAsTool()) {
            layerButton = new JButton(new ImageIcon(OMToolSet.class.getResource("layers.gif"), "Layer Controls"));
            layerButton.setBorderPainted(false);
            layerButton.setToolTipText("Layer Controls");
            layerButton.setMargin(new Insets(0,0,0,0));
            layerButton.addActionListener(getActionListener());
        }

        return layerButton;
    }
    
    /**
     * Get the ActionListener that triggers the LayersPanel. Useful
     * to have to provide an alternative way to bring up the
     * LayersPanel.
     *
     * _at_return ActionListener
     */
    public ActionListener getActionListener() {
        return new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    WindowSupport ws = getWindowSupport();
                    int w = 328;
                    int h = 300;
            
                    Dimension dim = ws.getComponentSize();
                    if (dim != null) {
                        w = (int)dim.getWidth();
                        h = (int)dim.getHeight();
                    }

                    int x = 10;
                    int y = 10;
            
                    Point loc = ws.getComponentLocation();
                    if (loc != null) {
                        x = (int) loc.getX();
                        y = (int) loc.getY();
                    }

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

                    ws.displayInWindow(frame, x, y, w, h);
                }
            };
    }

    /**
     * Set the layers that are in the LayersPanel. Make sure that the
     * layer[] is the same as that passed to any other OpenMap
     * component, like the LayersMenu. This method checks to see if
     * the layer[] has actually changed, in order or in size. If it
     * has, then createPanel() is called to rebuild the LayersPanel.
     *
     * _at_param inLayers the array of layers.
     */
    public void setLayers(Layer[] inLayers) {
        Layer[] layers = inLayers;

        if (inLayers == null) {
            layers = new Layer[0];
        }

        if (Debug.debugging("layerspanel")) {
            Debug.output("LayersPanel.setLayers() with " +
                         layers.length + " layers.");
        }

        LinkedList panes = getPanes();
        int separatorOffset = 0;
        if (backgroundLayerSeparator != null &&
            panes.contains(backgroundLayerSeparator)) {
            separatorOffset = 1;
        }

        if (panes.size() - separatorOffset != layers.length) {
            // if the panel hasn't been created yet, or if someone has
            // changed the layers on us, rebuild the panel.
            createPanel(layers);
            return;
        }

        int i = 0;
        Iterator it = panes.iterator();
        while (it.hasNext() && i < layers.length) {
            LayerPane pane = (LayerPane)it.next();

            if (pane == backgroundLayerSeparator) {
                continue;
            }

            if (pane.getLayer() != layers[i]) {
                // If the layer order sways at all, then we start over
                // and rebuild the panel
                createPanel(layers);
                return;
            } else {
                pane.updateLayerLabel();
            }

            // Do this just in case someone has changed something
            // somewhere else...
            pane.setLayerOn(layers[i].isVisible());
            i++;
        }

        // One last check for a mismatch...
        if (it.hasNext() || i < layers.length) {
            createPanel(layers);
        }
        // If we get here, it means that what we had is what we
        // wanted.
    }

    protected LinkedList getPanes() {
        if (panes == null) {
            panes = new LinkedList();
        }
        return panes;
    }

    protected void setPanes(LinkedList lpa) {
        panes = lpa;
    }

    /**
     * Create the panel that shows the LayerPanes. This method
     * creates the on/off buttons, palette buttons, and layer labels,
     * and adds them to the scrollPane used to display all the layers.
     *
     * _at_param inLayers the Layer[] that reflects all possible layers
     * that can be added to the map.
     */
    public void createPanel(Layer[] inLayers) {
        Debug.message("layerspanel", "LayersPanel.createPanel()");

        if (scrollPane != null) {
            remove(scrollPane);
        }

        Layer[] layers = inLayers;
        if (layers == null) {
            layers = new Layer[0];
        }

        if (panesPanel == null) {
            panesPanel = new JPanel();
            panesPanel.setLayout(new BoxLayout(panesPanel, BoxLayout.Y_AXIS));
            panesPanel.setAlignmentX(LEFT_ALIGNMENT);
            panesPanel.setAlignmentY(BOTTOM_ALIGNMENT);
        } else {
            ((BoxLayout)panesPanel.getLayout()).invalidateLayout(panesPanel);
            panesPanel.removeAll();
        }

         if (bg == null) {
            bg = new ButtonGroup();
        }

        LinkedList panes = new LinkedList();
        LinkedList backgroundPanes = new LinkedList();

        // populate the arrays of CheckBoxes and strings used to fill
        // the JPanel for the panes
        for (int i = 0; i < layers.length; i++) {
            Layer layer = layers[i];
            if (layer == null) {
                Debug.output("LayersPanel caught null layer, " + i +
                             " out of " + layers.length);
                continue;
            }

            LayerPane lpane = (LayerPane)paneLookUp.get(layer);

            if (lpane == null) {
                if (Debug.debugging("layercontrol")) {
                    Debug.output("LayersPanel: Creating LayerPane for " +
                                 layer.getName());
                }
                lpane = createLayerPaneForLayer(layer, layerHandler, bg);
                lpane.addPropertyChangeListener(LayerSelectedCmd, this);
                lpane.addPropertyChangeListener(LayerDeselectedCmd, this);
                paneLookUp.put(layer, lpane);
            } else {
                // In case this has been modified elsewhere...
                lpane.setLayerOn(layer.isVisible());
            }

            if (layer.getAddAsBackground() &&
                backgroundLayerSeparator != null) {
                backgroundPanes.add(lpane);
            } else {
                panes.add(lpane);
                panesPanel.add(lpane);
            }
        }

        if (backgroundPanes.size() != 0) {
            if (Debug.debugging("layerspanel")) {
                Debug.output("Adding BackgroundLayerSeparator");
            }
            panes.add(backgroundLayerSeparator);
            panesPanel.add(backgroundLayerSeparator);
            panes.addAll(backgroundPanes);

            Iterator it = backgroundPanes.iterator();
            while (it.hasNext()) {
                panesPanel.add((LayerPane)it.next());
            }

        } else if (backgroundLayerSeparator != null) {
            if (Debug.debugging("layerspanel")) {
                Debug.output("No layers are background layers, adding separator");
            }
            panes.add(backgroundLayerSeparator);
            panesPanel.add(backgroundLayerSeparator);
        }
        
        setPanes(panes);

        if (scrollPane != null) {
            remove(scrollPane);
            scrollPane.removeAll();
            scrollPane = null;
        }

        scrollPane = new JScrollPane(
            panesPanel,
            ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
            ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        add(scrollPane, BorderLayout.CENTER);
        revalidate();
    }

    /**
     * Called when a new LayerPane needs to be created for a layer.
     * You can use this to extend LayerPane and return something else
     * that fits your GUI.
     */
    protected LayerPane createLayerPaneForLayer(Layer layer,
                                                LayerHandler layerHandler,
                                                ButtonGroup bg) {
        if (showStatus) {
            return new LayerStatusPane(layer, layerHandler, bg, paletteButtonAsCheckBox);
        } else {
            return new LayerPane(layer, layerHandler, bg, paletteButtonAsCheckBox);
        }
    }

    public void deletePanes(LinkedList dpanes) {
        Debug.message("layerspanel", "LayersPanel.deletePanes()");
        if (dpanes != null) {
            paneLookUp.clear();
            Iterator it = dpanes.iterator();
            while (it.hasNext()) {
                LayerPane pane = (LayerPane)it.next();
                if (pane != null && pane != backgroundLayerSeparator) {
                    pane.removePropertyChangeListener(this);
                    pane.cleanup(bg);
                }
            }
        }

        // Shouldn't call this, but it's the only thing
        // that seems to make it work...
        if (Debug.debugging("helpgc")) {
            System.gc();
        }
    }

    /**
     * Set up the buttons used to move layers up and down, or
     * add/remove layers. The button component should hook itself up
     * to the LayersPanel, and assume that the LayersPanel has a
     * BorderLayout with the list in the center spot.
     */
    protected void createControlButtons() {
        controls = new LayerControlButtonPanel(this);
    }

    public void setControls(LayerControlButtonPanel lpb) {
        controls = lpb;
        if (lpb != null) {
            lpb.setLayersPanel(this);
        }
    }

    public LayerControlButtonPanel getControls() {
        return controls;
    }

    /**
     * Method associated with the ActionListener interface. This
     * method listens for action events meant to change the order of
     * the layers, as fired by the layer order buttons.
     *
     * _at_param e ActionEvent
     */
    public void actionPerformed(java.awt.event.ActionEvent e) {
        String command = e.getActionCommand();
        if (Debug.debugging("layerspanel")) {
            Debug.output("LayersPanel.actionPerformed(): " + command);
        }

        try {
            LayerPane pane = findSelectedPane();
            if (pane != null) {
                moveLayer(pane, command);
            }
        } catch (NullPointerException npe) {
        } catch (ArrayIndexOutOfBoundsException aioobe) {
        }
    }

    /**
     * Change a layer's position.
     */
    public void moveLayer(Layer layer, String command) {
        if (Debug.debugging("layercontrol")) {
            Debug.output("LayersPanel.moveLayer(): " + command +
                         " for " + layer.getName());
        }

        moveLayer((LayerPane)paneLookUp.get(layer), command);
    }


    /**
     * Change a layer's position, with the layer represented by a
     * LayerPane.
     */
    protected void moveLayer(LayerPane lp, String command) {

        if (lp == null) {

            if (Debug.debugging("layercontrol")) {
                Debug.output("LayersPanel.moveLayer(): LayerPane not represented on list");
            }

            if (command == LayerRemoveCmd) {
                // OK, here's a hidden trick. If no layers are selected
                // and the minus sign is clicked, then this is called.
                System.gc();
            }
            return;
        }

        LinkedList panes = getPanes();
        int row = panes.indexOf(lp);

        boolean boundary = false;
        int bls_row = -1;
        if (backgroundLayerSeparator != null) {
            bls_row = panes.indexOf(backgroundLayerSeparator);
            boundary = bufferedBoundary;
        }

        if (command.equals(LayerTopCmd)) {
            // Move layer selected layer to top
            panes.remove(lp);
            if (boundary && bls_row > 0 && row > bls_row + 1) {
                // If the backgroundLayerSeparator is more than one
                // above it, move to just below it on the first top
                // command.
                panes.add(bls_row + 1, lp);
            } else {
                panes.addFirst(lp);
            }

            rejiggerMapLayers();
        } else if (command.equals(LayerBottomCmd)) {
            // Move layer selected layer to bottom
            panes.remove(lp);

            if (boundary && bls_row > 0 && row < bls_row - 1) {
                // If the backgroundLayerSeparator is more than one
                // below it, move to just above it on the first top
                // command.
                panes.add(bls_row - 1, lp);
            } else {
                panes.addLast(lp);
            }

            rejiggerMapLayers();
        } else if (command.equals(LayerUpCmd)) {
            // Move layer selected layer up one
            if (row <= 0) return;
            panes.remove(row);
            panes.add(row - 1, lp);
            rejiggerMapLayers();
        } else if (command.equals(LayerDownCmd)) {
            // Move layer selected layer up one
            if (row < 0 || row == panes.size() - 1) return;
            panes.remove(row);
            panes.add(row + 1, lp);
            rejiggerMapLayers();
        } else if (command.equals(LayerRemoveCmd)) {

            if (layerHandler == null || !lp.getLayer().removeConfirmed()) {
                return;
            }

            // This order is somewhat important. lp.getLayer() will
            // be null after lp.cleanup. lp.setSelected() will cause
            // a series of property change notifications.
            lp.setSelected(false);
            lp.getLayer().setPaletteVisible(false);
            paneLookUp.remove(lp.getLayer());
            layerHandler.removeLayer(lp.getLayer());
            lp.cleanup(bg);

            // Shouldn't call this, but it's the only thing
            // that seems to make it work...
            if (Debug.debugging("helpgc")) {
                System.gc();
            }
            
            return;

        } else if (command.equals(LayerAddCmd)) {
            if (layerAddPanel != null) {
                layerAddPanel.showPanel();
            }
        }
    }

    /**
     * Find the selected LayerPane in the current LayerPane list.
     * Will return null if there isn't a selected pane.
     */
    protected LayerPane findSelectedPane() {
        Iterator it = getPanes().iterator();
        while (it.hasNext()) {
            LayerPane pane = (LayerPane)it.next();
            if (pane.isSelected()) {
                return pane;
            }
        }
        return null;
    }

   /**
     * Makes a new layer cake of active layers to send to
     * LayerHandler.setLayers().
     */
    protected void rejiggerMapLayers() {
        Debug.message("layerspanel", "LayersPanel.rejiggerMapLayers()");

        if (layerHandler == null) {
            // Why bother doing anything??
            return;
        }

        int selectedRow = -1;

        panesPanel.removeAll();

        LinkedList panes = getPanes();
        LinkedList layerList = new LinkedList();

        int bufferIndex = Integer.MAX_VALUE;

        int i = 0; // track layer index
        Iterator it = panes.iterator();
        while (it.hasNext()) {

            LayerPane pane = (LayerPane)it.next();

            if (pane == backgroundLayerSeparator) {
                panesPanel.add(backgroundLayerSeparator);
                bufferIndex = i++;
                continue;
            }

            Layer layer = pane.getLayer();
            layer.setAddAsBackground(i > bufferIndex);
            panesPanel.add(pane);
            layerList.add(layer);

            if (pane.isSelected()) {
                selectedRow = i;
            }
            i++;
        }

        scrollPane.revalidate();
        
        // Scroll up or down as necessary to keep selected row viewable
        if (selectedRow >= 0) {
            int spheight = scrollPane.getHeight();
            JScrollBar sb = scrollPane.getVerticalScrollBar();
            int sv = sb.getValue();
            int paneheight = ((LayerPane)panes.get(selectedRow)).getHeight();
            int rowvalue = selectedRow*paneheight;
            // Don't reset scrollBar unless the selected row
            // is not in the viewable range
            if (!((rowvalue > sv) && (rowvalue < spheight+sv))) {
                sb.setValue(rowvalue);
            }
        }

        Object[] layerArray = layerList.toArray();
        int length = layerArray.length;
        Layer[] newLayers = new Layer[length];

        for (int j = 0; j < length; j++) {
            newLayers[j] = (Layer)layerArray[j];
        }

        layerHandler.setLayers(newLayers);
    }

    /**
     * Update the layer names - if a layer name has changed, tell the
     * LayerPanes to check with their layers to update their labels.
     */
    public synchronized void updateLayerLabels() {
        Iterator it = getPanes().iterator();
        while (it.hasNext()) {
            ((LayerPane)it.next()).updateLayerLabel();
        }
    }

    public void propertyChange(PropertyChangeEvent pce) {
        String command = pce.getPropertyName();
        Object obj = pce.getNewValue();

        if (Debug.debugging("layercontrol")) {
            Debug.output("LayersPanel receiving PropertyChangeEvent " +
                         command + ", " + pce.toString());
        }

        if ((command == LayerSelectedCmd ||
             command == LayerDeselectedCmd) &&
            obj instanceof Layer) {

            if (Debug.debugging("layercontrol")) {
                Debug.output("LayersPanel: layer panel notification that layer is selected: " + ((Layer)obj).getName());
            }
            firePropertyChange(command, null, ((Layer)obj));

        } else if ((command == LayersPanel.LayerTopCmd ||
                    command == LayersPanel.LayerBottomCmd ||
                    command == LayersPanel.LayerUpCmd ||
                    command == LayersPanel.LayerDownCmd ||
                    command == LayersPanel.LayerRemoveCmd) &&
                   obj instanceof Layer) {
            if (Debug.debugging("layercontrol")) {
                Debug.output("LayersPanel: layer panel notification that layer should be raised: " + ((Layer)obj).getName());
            }
            moveLayer((Layer)obj, command);
        }
    }
    
    /**
     * Called when the LayersPanel is added the BeanContext, or when
     * another object is added to the BeanContext after the
     * LayerHandler has been added. This allows the LayersPanel to
     * keep up-to-date with any objects that it may be interested in,
     * namely, the LayerHandler. If a LayerHandler has already been
     * added, the new LayerHandler will replace it.
     *
     * _at_param someObj the object being added to the BeanContext
     */
    public void findAndInit(Object someObj) {
        if (someObj instanceof LayerHandler) {
            // do the initializing that need to be done here
            Debug.message("layerspanel","LayersPanel found a LayerHandler");
            setLayerHandler((LayerHandler)someObj);
        }

        if (someObj instanceof BufferedLayerMapBean) {
            if (Debug.debugging("layerspanel")) {
                Debug.output("LayersPanel found BufferedLayerMapBean, creating separator panel");
            }
            backgroundLayerSeparator = LayerPane.getBackgroundLayerSeparator(" --- Background Layers --- ");
        }

        // Don't want to forward ourselves on to controls, supposedly
        // they already know.
        if (controls instanceof LightMapHandlerChild && someObj != this) {
            ((LightMapHandlerChild)controls).findAndInit(someObj);
        }
    }

    /**
     * BeanContextMembershipListener method. Called when an object
     * has been removed from the parent BeanContext. If a
     * LayerHandler is removed, and it's the current one being
     * listened to, then the layers in the panel will be wiped clean.
     *
     * _at_param someObj the object being removed from the BeanContext
     */
    public void findAndUndo(Object someObj) {
        if (someObj instanceof LayerHandler) {
            // do the initializing that need to be done here
            Debug.message("layerspanel","LayersPanel removing LayerHandler");
            if (getLayerHandler() == (LayerHandler) someObj) {
                setLayerHandler(null);
            }
        }

        // Don't want to forward ourselves on to controls, supposedly
        // they already know.
        if (controls instanceof LightMapHandlerChild && someObj != this) {
            ((LightMapHandlerChild)controls).findAndUndo(someObj);
        }
    }

    public void setProperties(String prefix, Properties props) {
        super.setProperties(prefix, props);
        prefix = PropUtils.getScopedPropertyPrefix(prefix);

        String controlString =
            props.getProperty(prefix + ControlButtonsProperty);

        if (controlString != NO_CONTROLS) {
            if (controlString == null) {
                createControlButtons();
            } else {
                Object obj = ComponentFactory.create(
                    controlString, prefix + ControlButtonsProperty, props);
                
                if (obj instanceof LayerControlButtonPanel) {
                    setControls((LayerControlButtonPanel)obj);
                }
            }
        }

        bufferedBoundary = LayerUtils.booleanFromProperties(props, prefix + BufferedBoundaryProperty, bufferedBoundary);
        showStatus = LayerUtils.booleanFromProperties(props, prefix + ShowStatusProperty, showStatus);
        paletteButtonAsCheckBox = LayerUtils.booleanFromProperties(props, prefix + PaletteButtonAsCheckBoxProperty, paletteButtonAsCheckBox);
    }

    public Properties getProperties(Properties props) {
        props = super.getProperties(props);


        String prefix = PropUtils.getScopedPropertyPrefix(this);
        LayerControlButtonPanel controls = getControls();
        if (controls != null) {
            props.put(prefix + ControlButtonsProperty, controls.getClass().getName());
            controls.getProperties(props);
        }
        props.put(prefix + BufferedBoundaryProperty, new Boolean(bufferedBoundary).toString());
        props.put(prefix + ShowStatusProperty, new Boolean(showStatus).toString());
        props.put(prefix + PaletteButtonAsCheckBoxProperty, new Boolean(paletteButtonAsCheckBox).toString());
        return props;
    }

    public Properties getPropertyInfo(Properties props) {
        props = super.getPropertyInfo(props);
        props.put(ControlButtonsProperty, "Class to use for layer control buttons (Optional)");
        LayerControlButtonPanel controls = getControls();
        if (controls != null) {
            controls.getPropertyInfo(props);
        }
        props.put(BufferedBoundaryProperty, "Force layer movement to respect background layer boundary.");
        props.put(BufferedBoundaryProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
        props.put(ShowStatusProperty, "Use Layer Panes that show layer status.");
        props.put(ShowStatusProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
        props.put(PaletteButtonAsCheckBoxProperty, "If palette button should behave as a check box or as a simple button.");
        props.put(PaletteButtonAsCheckBoxProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
        return props;
    }
}



// **********************************************************************
//
// <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/gui/ProjectionStackTool.java,v $
// $RCSfile: ProjectionStackTool.java,v $
// $Revision: 1.1.1.1 $
// $Date: 2003/03/03 21:58:00 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.gui;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;

import com.bbn.openmap.proj.*;
import com.bbn.openmap.util.Debug;

/**
 * The ProjectionStackTool contains buttons that can trigger a
 * ProjectionStack to change a projection of a MapBean to a previous
 * projection, or to a later projection if the active projection is in
 * the middle of the stack. The OpenMap ProjectionStack will look for
 * one of these, and connect itself to it if it finds one.
 */
public class ProjectionStackTool extends OMToolComponent
    implements ProjectionStackTrigger {
    
    protected Vector listeners;

    protected JButton backButton;
    protected JButton forwardButton;
// protected JButton clearButton;

    protected static transient String backName = "backproj.gif";
    protected static transient String forwardName = "forwardproj.gif";
    protected static transient String dimBackName = "dimbackproj.gif";
    protected static transient String dimForwardName = "dimforwardproj.gif";
    
    protected boolean dimBackButton = true;
    protected boolean dimForwardButton = true;

    ImageIcon backIcon;
    ImageIcon dimBackIcon;
    ImageIcon forwardIcon;
    ImageIcon dimForwardIcon;

    public ProjectionStackTool() {
        super();
        setKey("projectionstacktool");
        resetButtons(!dimBackButton, !dimForwardButton);
        add(backButton);
        add(forwardButton);
// add(clearButton);
    }

    /**
     * Add an ActionListener for events that trigger events to shift
     * the Projection stack. If you are hooking up a ProjectionStack,
     * you don't need to call this. The ProjectionStack will call
     * this when you can addProjectionStackTrigger on it.
     */
    public void addActionListener(ActionListener al) {
        if (backButton != null && forwardButton != null) {
            backButton.addActionListener(al);
            forwardButton.addActionListener(al);
// clearButton.addActionListener(al);
        } else {
            if (listeners == null) {
                listeners = new Vector();
            }
            listeners.add(al);
        }
    }

    /**
     * Remove an ActionListener that receives events that trigger
     * events to shift the Projection stack. If you are hooking up a
     * ProjectionStack, you don't need to call this. The
     * ProjectionStack will call this when you can
     * removeProjectionStackTrigger on it.
     */
    public void removeActionListener(ActionListener al) {
        if (backButton != null && forwardButton != null) {
            backButton.removeActionListener(al);
            forwardButton.removeActionListener(al);
// clearButton.removeActionListener(al);
        } else if (listeners != null) {
            listeners.remove(al);
        }
    }

    /**
     * To receive a status to let the trigger know if any projections
     * in the forward or backward stacks exist, possibly to disable
     * any gui widgets.
     *
     * _at_param containsBackProjections there is at least one past
     * projection in the back cache.
     * _at_param containsForwardProjections there is at least one future
     * projection in the forward cache. Used when a past projection
     * is being used.
     */
    public void updateProjectionStackStatus(boolean containsBackProjections,
                                            boolean containsForwardProjections) {
        dimBackButton = !containsBackProjections;
        dimForwardButton = !containsForwardProjections;
        resetButtons(containsBackProjections, containsForwardProjections);
    }

    public void resetButtons(boolean enableBackButton,
                             boolean enableForwardButton) {
        
        java.net.URL url;
        JButton b;

        if (backIcon == null) {
            url = getClass().getResource(backName);
            backIcon = new ImageIcon(url);
        }

        if (dimBackIcon == null) {
            url = getClass().getResource(dimBackName);
            dimBackIcon = new ImageIcon(url);
        }

        if (forwardIcon == null) {
            url = getClass().getResource(forwardName);
            forwardIcon = new ImageIcon(url);
        }

        if (dimForwardIcon == null) {
            url = getClass().getResource(dimForwardName);
            dimForwardIcon = new ImageIcon(url);
        }

        ImageIcon active;
        String toolTip;
        String disabled = " (" + i18n.get(ProjectionStackTool.class,"disabled","disabled") + ")";
        int size;

        toolTip = i18n.get(ProjectionStackTool.class,"backTip","Go back to previous projection");
        if (enableBackButton) {
            active = backIcon;
        } else {
            active = dimBackIcon;
            toolTip += " " + disabled;
        }

        if (backButton == null) {
            backButton = new JButton(active);
            backButton.setMargin(new Insets(0,0,0,0));
            backButton.setBorderPainted(false);
            backButton.setActionCommand(ProjectionStack.BackProjCmd);
            if (listeners != null) {
                size = listeners.size();
                for (int i = 0; i < size; i++) {
                    backButton.addActionListener((ActionListener)listeners.elementAt(i));
                }
            }

        } else {
            backButton.setIcon(active);
        }
        backButton.setToolTipText(toolTip);

        toolTip = i18n.get(ProjectionStackTool.class,"forwardTip","Go forward to next projection");
        if (enableForwardButton) {
            active = forwardIcon;
        } else {
            active = dimForwardIcon;
            toolTip += " " + disabled;
        }

        if (forwardButton == null) {
            forwardButton = new JButton(active);
            forwardButton.setMargin(new Insets(0,0,0,0));
            forwardButton.setBorderPainted(false);
            forwardButton.setActionCommand(ProjectionStack.ForwardProjCmd);

            if (listeners != null) {
                size = listeners.size();
                for (int i = 0; i < size; i++) {
                    forwardButton.addActionListener((ActionListener)listeners.elementAt(i));
                }
            }
        } else {
            forwardButton.setIcon(active);
        }
        forwardButton.setToolTipText(toolTip);

// if (clearButton == null) {
// clearButton = new JButton("Clear Stack");
// clearButton.setMargin(new Insets(0,0,0,0));
// clearButton.setBorderPainted(false);
// clearButton.setActionCommand(ProjectionStack.ClearStacksCmd);

// if (listeners != null) {
// size = listeners.size();
// for (int i = 0; i < size; i++) {
// clearButton.addActionListener((ActionListener)listeners.elementAt(i));
// }
// }
// }
    }
    
    public void findAndInit(Object someObj) {
        if (someObj instanceof ProjectionStack) {
            Debug.message("projectionstacktrigger","ProjectionStackTrigger adding a ProjectionStack");
            ((ProjectionStack)someObj).addProjectionStackTrigger(this);
        }
    }

    public void findAndUndo(Object someObj) {
        if (someObj instanceof ProjectionStack) {
            Debug.message("projectionstacktrigger","ProjectionStackTrigger removing a ProjectionStack");
            ((ProjectionStack)someObj).removeProjectionStackTrigger(this);
        }
    }
}



// **********************************************************************
//
// <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/OMComponentPanel.java,v $
// $RCSfile: OMComponentPanel.java,v $
// $Revision: 1.6 $
// $Date: 2004/10/14 18:18:16 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.gui;

import java.beans.*;
import java.beans.beancontext.*;
import java.util.Iterator;
import java.util.Properties;
import javax.swing.*;

import com.bbn.openmap.*;

/**
 * The OMComponentPanel is a convienent super class intended to
 * provide an easy way to extend JPanel while also implementing the
 * common functions of an OMComponent (PropertyConsumer,
 * BeanContextMembershipListener and BeanContextChild). The
 * PropertyListener methods in the BeanContextChild aren't needed,
 * because the java.awt.Component provides them.
 */
public abstract class OMComponentPanel extends JPanel implements
        PropertyConsumer, BeanContextChild, BeanContextMembershipListener,
        LightMapHandlerChild {

    /**
     * All OMComponentPanels have access to an I18n object, which is
     * provided by the Environment.
     */
    protected I18n i18n = Environment.getI18n();

    /**
     * BeanContextChildSupport object provides helper functions for
     * BeanContextChild interface.
     */
    protected BeanContextChildSupport beanContextChildSupport = new BeanContextChildSupport();

    protected OMComponentPanel() {
        super();
    }

    protected WindowSupport windowSupport;

    public void setWindowSupport(WindowSupport ws) {
        windowSupport = ws;
    }

    public WindowSupport getWindowSupport() {
        return windowSupport;
    }

    ///////////////////////////////////////////////////////////////////////////
    //// PropertyConsumer methods
    ///////////////////////////////////////////////////////////////////////////

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

    /**
     * Sets the properties for the OMComponent.
     *
     * _at_param props the <code>Properties</code> object.
     */
    public void setProperties(java.util.Properties props) {
        setProperties(getPropertyPrefix(), props);
    }

    /**
     * Sets the properties for the OMComponent.
     *
     * _at_param prefix the token to prefix the property names
     * _at_param props the <code>Properties</code> object
     */
    public void setProperties(String prefix, java.util.Properties props) {
        setPropertyPrefix(prefix);

        // String realPrefix =
        // PropUtils.getScopedPropertyPrefix(prefix);
    }

    /**
     * PropertyConsumer method, to fill in a Properties object,
     * reflecting the current values of the OMComponent. If the
     * component has a propertyPrefix set, the property keys should
     * have that prefix plus a separating '.' prepended to each
     * propery key it uses for configuration.
     *
     * _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);

        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();
        }
        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.
     *
     * _at_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.
     *
     * _at_return the property prefix for the panel
     */
    public String getPropertyPrefix() {
        return propertyPrefix;
    }

    ///////////////////////////////////////////////////////////////////////////
    //// MapHandlerChild methods to make the tool work with
    //// the MapHandler to find any SelectionProviders.
    ///////////////////////////////////////////////////////////////////////////

    public void findAndInit(Object obj) {}

    public void findAndUndo(Object obj) {}

    /**
     * This is the method that your object can use to find other
     * objects within the MapHandler (BeanContext). This method gets
     * called when the object gets added to the MapHandler, or when
     * another object gets added to the MapHandler after the object is
     * a member.
     *
     * _at_param it Iterator to use to go through a list of objects. Find
     * the ones you need, and hook yourself up.
     */
    public void findAndInit(Iterator it) {
        while (it.hasNext()) {
            findAndInit(it.next());
        }
    }

    /**
     * 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());
        }
    }

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

    /**
     * Method for BeanContextChild interface. Adds this object as a
     * BeanContextMembership listener, set the BeanContext in this
     * objects BeanContextSupport, and receives the initial list of
     * objects currently contained in the BeanContext.
     */
    public void setBeanContext(BeanContext in_bc) throws PropertyVetoException {

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

    /**
     * Method for BeanContextChild interface. 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);
    }
}

# Sample ResourceBundle properties file

CoordPanel.border=Decimal Degrees

CoordPanel.lonlabel=Longitude:\

CoordPanel.latlabel=Latitude:\

CombinedCoordPanel.closebutton=Close

CombinedCoordPanel.applybutton=Apply

CombinedCoordPanel.tabPane.mgrs=MRGS

CombinedCoordPanel.tabPane.decdeg=Dec Deg

CombinedCoordPanel.tabPane.dms=DMS

CombinedCoordPanel.tabPane.utm=UTM

MGRSCoordPanel.border=MGRS Coordinate

MGRSCoordPanel.mgrsLabel=MGRS:\

DMSCoordPanel.border=Degress|Minutes|Seconds

DMSCoordPanel.latlabel=Latitude DMS:\

DMSCoordPanel.lonlabel=Longitude DMS:\

UTMCoordPanel.border=Zone Number|Hemisphere|Easting|Northing

UTMCoordPanel.zone.tooltip=Zone Number: 0-60

UTMCoordPanel.hemi.tooltip=Hemisphere: N or S

UTMCoordPanel.utmLabel=UTM:\

CoordDialog.defaultTitle=Go To Coordinates

CombinedCoordPanel.defaultComment=Set Center of Map to Coordinates:

NavigatePanel.panel.northwest=Pan Northwest

NavigatePanel.panel.north=Pan North

NavigatePanel.panel.northeast=Pan NortEast

NavigatePanel.panel.west=Pan West

NavigatePanel.panel.centerAtStart=Center Map at Starting Coords

NavigatePanel.panel.east=Pan East

NavigatePanel.panel.southwest=Pan Southwest

NavigatePanel.panel.south=Pan South

NavigatePanel.panel.southeast=Pan Southeast

ProjectionStackTool.backTip=Go back to previous projection

ProjectionStackTool.forwardTip=Go forward to next projection

ProjectionStackTool.disabled=disabled

LayerControlButtonPanel.moveLayerToTop=Move selected layer to top

LayerControlButtonPanel.moveLayerUpOne=Move selected layer up one

LayerControlButtonPanel.moveLayerToBottom=Move selected layer to bottom

LayerControlButtonPanel.moveLayerDownOne=Move selected layer down one

LayerControlButtonPanel.removeLayer=Remove selected layer

LayerControlButtonPanel.addLayer=Add a layer


# Sample ResourceBundle properties file

CoordPanel.border=Stopnie dziesi\u0119tne

CoordPanel.lonlabel=D\u0142ugo\u015B\u0107:\

CoordPanel.latlabel=Szeroko\u015B\u0107:\

CombinedCoordPanel.closebutton=Rezygnuj

CombinedCoordPanel.applybutton=Akceptuj

CombinedCoordPanel.tabPane.mgrs=MRGS

CombinedCoordPanel.tabPane.decdeg=\ \ \u00B0\ \

CombinedCoordPanel.tabPane.dms=\ \u00B0 ' "\

CombinedCoordPanel.tabPane.utm=UTM

MGRSCoordPanel.border=MGRS wsp\u00F3\u0142rz\u0119dne

MGRSCoordPanel.mgrsLabel=MGRS:\

DMSCoordPanel.border=Stopnie|Minuty|Sekundy

DMSCoordPanel.latlabel=Szeroko\u015B\u0107 SMS:\

DMSCoordPanel.lonlabel=D\u0142ugo\u015B\u0107 SMS:\

UTMCoordPanel.border=Zone Number|Zone Letter|Easting|Northing

UTMCoordPanel.utmLabel=UTM:\

CoordDialog.defaultTitle=Skocz do lokalizacji

CombinedCoordPanel.defaultComment=Ustaw wsp\u00F3\u0142rz\u0119dne \u015Brodka mapy:

NavigatePanel.panel.northwest=Przesu\u0144 na p\u00F3\u0142nocny zach\u00F3d

NavigatePanel.panel.north=Przesu\u0144 na p\u00F3\u0142noc

NavigatePanel.panel.northeast=Przesu\u0144 na p\u00F3\u0142nocny wsch\u00F3d

NavigatePanel.panel.west=Przesu\u0144 na zach\u00F3d

NavigatePanel.panel.centerAtStart=Wr\u00F3\u0107 na pocz\u0105tkow\u0105 pozycj\u0119

NavigatePanel.panel.east=Przesu\u0144 na wsch\u00F3d

NavigatePanel.panel.southwest=Przesu\u0144 na po\u0142udniowy zach\u00F3d

NavigatePanel.panel.south=Przesu\u0144 na po\u0142udnie

NavigatePanel.panel.southeast=Przesu\u0144 na po\u0142udniowy wsch\u00F3d

ProjectionStackTool.backTip=Przejd\u017A do poprzedniej pozycji

ProjectionStackTool.forwardTip=Przejd\u017A do nast\u0119pnej pozycji
ProjectionStackTool.disabled=niedost\u0119pne

LayerControlButtonPanel.moveLayerToTop=Przesu\u0144 zaznaczon\u0105 warstw\u0119 na pocz\u0105tek

LayerControlButtonPanel.moveLayerUpOne=Przesu\u0144 zaznaczon\u0105 warstw\u0119 o 1 w g\u00F3r\u0119

LayerControlButtonPanel.moveLayerToBottom=Przesu\u0144 zaznaczon\u0105 warstw\u0119 na koniec

LayerControlButtonPanel.moveLayerDownOne=Przesu\u0144 zaznaczon\u0105 warstw\u0119 o 1 w d\u00F3\u0142

LayerControlButtonPanel.removeLayer=Usu\u0144 zaznaczon\u0105 warstw\u0119

LayerControlButtonPanel.addLayer=Dodaj warstw\u0119


# Sample ResourceBundle properties file

MouseModeMenu.mouseModeMenu=Mouse Mode

ProjectionMenu.projectionMenu=Projection


# Sample ResourceBundle properties file

MouseModeMenu.mouseModeMenu=Tryb pracy myszy

ProjectionMenu.projectionMenu=Odwzorowanie


# Sample ResourceBundle properties file

DrawingAttributes.lineColor=Line color

DrawingAttributes.lineColor.tooltip=Edge color for graphics.

DrawingAttributes.fillColor=Fill color

DrawingAttributes.fillColor.tooltip=Fill color for graphics.

DrawingAttributes.selectColor=Selected color

DrawingAttributes.selectColor.tooltip=Selected edge color for graphics.

DrawingAttributes.mattingColor=Matting color

DrawingAttributes.mattingColor.tooltip=Matting edge color for graphics.

DrawingAttributes.fillPattern=Image fill pattern

DrawingAttributes.fillPattern.tooltip=Image file to use for fill pattern for graphics (optional).

DrawingAttributes.lineWidth=Line width

DrawingAttributes.lineWidth.tooltip=Line width for edges of graphics

DrawingAttributes.dashPattern=Dash pattern

DrawingAttributes.dashPattern.tooltip=Line dash pattern, represented by space separated numbers (on off on ...)

DrawingAttributes.dashPhase=Dash phase

DrawingAttributes.dashPhase.tooltip=Phase for dash pattern (Default is 0)

DrawingAttributes.baseScale=Base scale

DrawingAttributes.baseScale.tooltip=<HTML><BODY>Scale which should be used as the base scale for the <br>patterns and line width. If set, size of pattern and <br>widths will be adjusted to the map scale</BODY></HTML>

DrawingAttributes.matted=Matted

DrawingAttributes.matted.tooltip=Flag to enable a thin black matting to be drawn around graphics..

DrawingAttributes.chooseLineColor=Choose Line Color

DrawingAttributes.chooseMattingColor=Choose Matting Color

DrawingAttributes.chooseFillColor=Choose Fill Color

DrawingAttributes.chooseSelectColor=Choose Select Color

DrawingAttributes.lineButton.tooltip=Modify Line Parameters

DrawingAttributes.mattedCheckBox.tooltip=Enable/Disable Matting on Edge

DrawingAttributes.mattingColorButton.tooltip=Change Matted Edge Color (true/opaque)

DrawingAttributes.selectColorButton.tooltip=Change Highlight Edge Color (true/opaque)

DrawingAttributes.fillColorButton.tooltip=Change Fill Color (true/opaque)

DrawingAttributes.lineColorButton.tooltip=Change Edge Color (true/opaque)


# Sample ResourceBundle properties file

DrawingAttributes.lineColor=Kolor linii

DrawingAttributes.lineColor.tooltip=Kolor krawedzi dla grafiki

DrawingAttributes.fillColor=Kolor wype\u0142nienia

DrawingAttributes.fillColor.tooltip=Kolor wype\u0142nienia dla grafiki

DrawingAttributes.selectColor=Kolor wybranej linii

DrawingAttributes.selectColor.tooltip=Kolor wybranje linii na obiekcie graficznym

DrawingAttributes.mattingColor=Kolor cieniowania

DrawingAttributes.mattingColor.tooltip=Kolor cieniowania linii

DrawingAttributes.fillPattern=Wzorzec wype\u0142nienia (obrazek)

DrawingAttributes.fillPattern.tooltip=Obrazek u\u017Cywany jako wype\u0142nienie t\u0142a (opcjonalnie)

DrawingAttributes.lineWidth=Szeroko\u015B\u0107 linii

DrawingAttributes.lineWidth.tooltip=Szeroko\u015B\u0107 lini dla kraw\u0119dzi rysunku

DrawingAttributes.dashPattern=Wzorzec linii przerywanej

DrawingAttributes.dashPattern.tooltip=Wzorzec linii przerywanej, reprezentowany jako spacja odzielana liczbami (linia przerwa lnia...)

DrawingAttributes.dashPhase=Przesuni\u0119cie linii

DrawingAttributes.dashPhase.tooltip=Przesuni\u0119cie lini przerywanej (domy\u015Blnie 0)

DrawingAttributes.baseScale=Skala podstawowa

DrawingAttributes.baseScale.tooltip=<HTML><BODY>Skala, kt\u00F3ra b\u0119dzie u\u017Cywana jak skala dla <br>wzor\u00F3w i grubo\u015Bci linii. Je\u015Bli ustawiona, rozmairy wzor\u00F3w oraz <br>grubo\u015Bci linii zostana dostosowane do skali mapy.</BODY></HTML>

DrawingAttributes.matted=Cieniowanie

DrawingAttributes.matted.tooltip=Znacznik, okre\u015Blaj\u0105cy czy ma by\u0107 rysowana cieniutka siatka dooko\u0142a grafiki

DrawingAttributes.chooseLineColor=Wybierz kolor linii

DrawingAttributes.chooseMattingColor=Wybierz kolor cieniowania

DrawingAttributes.chooseFillColor=Wybierz kolor wype\u0142nienia

DrawingAttributes.chooseSelectColor=Wybierz kolor selekcji

DrawingAttributes.lineButton.tooltip=Modyfikacja parametr\u00F3w linii

DrawingAttributes.mattedCheckBox.tooltip=W\u0142\u0105cz/wy\u0142\u0105cz cieniowanie na kraw\u0119dziach

DrawingAttributes.mattingColorButton.tooltip=Zmiana koloru cieniowania lini (prawdziwe / nieprze\u017Aroczyste)

DrawingAttributes.selectColorButton.tooltip=Zmiana koloru linii pod\u015Bwietlenia (prawdziwe / nieprze\u017Aroczyste)

DrawingAttributes.fillColorButton.tooltip=Zmiana koloru wype\u0142nienia (prawdziwe / nieprze\u017Aroczyste)

DrawingAttributes.lineColorButton.tooltip=Zmiana koloru kraw\u0119dzi (prawdziwe / nieprze\u017Aroczyste)


// **********************************************************************
//
// <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/layer/GraticuleLayer.java,v $
// $RCSfile: GraticuleLayer.java,v $
// $Revision: 1.7 $
// $Date: 2004/02/05 18:16:10 $
// $Author: dietrick $
//
// **********************************************************************

// Modified 28 September 2002 by David N. Allsopp to allow font size
// to be changed. See sections commented with 'DNA'.

package com.bbn.openmap.layer;

import java.awt.*;
import java.awt.event.*;
import java.util.Properties;
import javax.swing.*;

import com.bbn.openmap.*;
import com.bbn.openmap.event.*;
import com.bbn.openmap.omGraphics.*;
import com.bbn.openmap.proj.*;
import com.bbn.openmap.util.*;

/**
 * Layer that draws graticule lines. If the showRuler property is
 * set to true, then longitude values are displayed on the bottom of
 * the map, and latitude values are displayed on the left side. If
 * the show1And5Lines property is true, then 5 degree lines are drawn
 * when there are &lt;= threshold ten degree latitude or longitude lines,
 * and 1 degree lines are drawn when there are &lt;= threshold five
 * degree latitude or longitude degree lines.
 *
 * <P> The openmap.properties file can control the layer with the
 * following settings:
 * <code><pre>
 * # Show lat / lon spacing labels
 * graticule.showRuler=true
 * graticule.show1And5Lines=true
 * # Controls when the five degree lines and one degree lines kick in
 * #- when there is less than the threshold of ten degree lat or lon
 * #lines, five degree lines are drawn. The same relationship is there
 * #for one to five degree lines.
 * graticule.threshold=2
 * # the color of 10 degree spacing lines (Hex ARGB)
 * graticule.10DegreeColor=FF000000
 * # the color of 5 degree spacing lines (Hex ARGB)
 * graticule.5DegreeColor=C7009900
 * # the color of 1 degree spacing lines (Hex ARGB)
 * graticule.1DegreeColor=C7003300
 * # the color of the equator (Hex ARGB)
 * graticule.equatorColor=FFFF0000
 * # the color of the international dateline (Hex ARGB)
 * graticule.datelineColor=7F000099
 * # the color of the special lines (Hex ARGB)
 * graticule.specialLineColor=FF000000
 * # the color of the labels (Hex ARGB)
 * graticule.textColor=FF000000
 * </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=graticule ...
 * # class
 * graticule.class=com.bbn.openmap.layer.GraticuleLayer
 * # name
 * graticule.prettyName=Graticule
 * </pre></code>
 *
 */
public class GraticuleLayer extends OMGraphicHandlerLayer
    implements ActionListener {

        
    protected I18n i18n = Environment.getI18n();
    // default to not showing the ruler (mimicing older GraticuleLayer)
    protected boolean defaultShowRuler = true;
    protected boolean defaultShowOneAndFiveLines = true;
    protected boolean defaultShowBelowOneLines = false;
    protected int defaultThreshold = 2;

    /**
     * Flag for lineType - true is LINETYPE_STRAIGHT, false is
     * LINETYPE_GREATCIRCLE.
     */
    protected boolean boxy = true;
    /**
     * Threshold is the total number of ten lines on the screen before the
     * five lines appear, and the total number of five lines on the screen
     * before the one lines appear.
     */
    protected int threshold = defaultThreshold;
    /** The ten degree latitude and longitude lines, premade. */
    protected OMGraphicList tenDegreeLines = null;
    /** The equator, dateline and meridian lines, premade. */
    protected OMGraphicList markerLines = null;

    private final static int SHOW_TENS = 0;
    private final static int SHOW_FIVES = 1;
    private final static int SHOW_ONES = 2;

    protected boolean showOneAndFiveLines = defaultShowOneAndFiveLines;
    protected boolean showBelowOneLines = defaultShowBelowOneLines;
    protected boolean showRuler = defaultShowRuler;

// protected Font font = new Font("Helvetica", java.awt.Font.PLAIN, 10);
    protected Font font = null;
    protected int fontSize = 10;

    // Color variables for different line types
    protected Color tenDegreeColor = null;
    protected Color fiveDegreeColor = null;
    protected Color oneDegreeColor = null;
    protected Color belowOneDegreeColor = null;
    protected Color equatorColor = null;
    protected Color dateLineColor = null;
    protected Color specialLineColor = null; // Tropic of Cancer, Capricorn
    protected Color textColor = null;

    // Default colors to use, if not specified in the properties.
    protected String defaultTenDegreeColorString = "000000";
    protected String defaultFiveDegreeColorString = "33009900";
    protected String defaultOneDegreeColorString = "33003300";
    protected String defaultBelowOneDegreeColorString = "9900ff00";
    protected String defaultEquatorColorString = "990000";
    protected String defaultDateLineColorString = "000099";
    protected String defaultSpecialLineColorString = "000000";
    protected String defaultTextColorString = "000000";

    // property text values
    public static final String TenDegreeColorProperty = "10DegreeColor";
    public static final String FiveDegreeColorProperty = "5DegreeColor";
    public static final String OneDegreeColorProperty = "1DegreeColor";
    public static final String BelowOneDegreeColorProperty = "Below1DegreeColor";
    public static final String EquatorColorProperty = "equatorColor";
    public static final String DateLineColorProperty = "datelineColor";
    public static final String SpecialLineColorProperty = "specialLineColor";
    public static final String TextColorProperty = "textColor";
    public static final String ThresholdProperty = "threshold";
    public static final String ShowRulerProperty = "showRuler";
    public static final String ShowOneAndFiveProperty = "show1And5Lines";
    public static final String ShowBelowOneProperty = "showBelow1Lines";
    public static final String FontSizeProperty = "fontSize"; //DNA

    /**
     * Construct the GraticuleLayer.
     */
    public GraticuleLayer() {
        // precalculate for boxy
        boxy = true;
        setName("Graticule");
    }

    /**
     * The properties and prefix are managed and decoded here, for
     * the standard uses of the GraticuleLayer.
     *
     * _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);

        tenDegreeColor = PropUtils.parseColorFromProperties(properties,
                                                            prefix + TenDegreeColorProperty,
                                                            defaultTenDegreeColorString);

        fiveDegreeColor = PropUtils.parseColorFromProperties(properties,
                                                             prefix + FiveDegreeColorProperty,
                                                             defaultFiveDegreeColorString);
        
        oneDegreeColor = PropUtils.parseColorFromProperties(properties,
                                                            prefix + OneDegreeColorProperty,
                                                            defaultOneDegreeColorString);
        
        belowOneDegreeColor = PropUtils.parseColorFromProperties(properties,
                                                            prefix + BelowOneDegreeColorProperty,
                                                            defaultBelowOneDegreeColorString);
        
        equatorColor = PropUtils.parseColorFromProperties(properties,
                                                          prefix + EquatorColorProperty,
                                                          defaultEquatorColorString);
        
        dateLineColor = PropUtils.parseColorFromProperties(properties,
                                                           prefix + DateLineColorProperty,
                                                           defaultDateLineColorString);
        
        specialLineColor = PropUtils.parseColorFromProperties(properties,
                                                              prefix + SpecialLineColorProperty,
                                                              defaultSpecialLineColorString);
        
        textColor = PropUtils.parseColorFromProperties(properties,
                                                       prefix + TextColorProperty,
                                                       defaultTextColorString);

        threshold = PropUtils.intFromProperties(properties,
                                                prefix + ThresholdProperty,
                                                defaultThreshold);

        fontSize = PropUtils.intFromProperties(properties,
                                               prefix + FontSizeProperty,
                                               fontSize);


        font = new Font("Helvetica", java.awt.Font.PLAIN, fontSize);

        setShowOneAndFiveLines(PropUtils.booleanFromProperties(properties,
                                                               prefix + ShowOneAndFiveProperty,
                                                               defaultShowOneAndFiveLines));
        
        setShowBelowOneLines(PropUtils.booleanFromProperties(properties,
                                                               prefix + ShowBelowOneProperty,
                                                               defaultShowBelowOneLines));
        
        setShowRuler(PropUtils.booleanFromProperties(properties,
                                                     prefix + ShowRulerProperty,
                                                     defaultShowRuler));

        // So they will get re-created.
        tenDegreeLines = null;
        markerLines = null;
    }

    protected JCheckBox showRulerButton = null;
    protected JCheckBox show15Button = null;
    protected JCheckBox showBelow1Button = null;
    
    public void setShowOneAndFiveLines(boolean set) {
        showOneAndFiveLines = set;
        if (show15Button != null) {
            show15Button.setSelected(set);
        }
    }

    public void setShowBelowOneLines(boolean set) {
        showBelowOneLines = set;
        if (showBelow1Button != null) {
            showBelow1Button.setSelected(set);
        }
    }
    
    public boolean getShowOneAndFiveLines() {
        return showOneAndFiveLines;
    }
    
    public boolean getShowBelowOneLines() {
        return showBelowOneLines;
    }

    public void setShowRuler(boolean set) {
        showRuler = set;
        if (showRulerButton != null) {
            showRulerButton.setSelected(set);
        }
    }

    public boolean getShowRuler() {
        return showRuler;
    }

    /**
     * The properties and prefix are managed and decoded here, for
     * the standard uses of the GraticuleLayer.
     *
     * _at_param properties the properties set in the properties file.
     */
    public Properties getProperties(Properties properties) {
        properties = super.getProperties(properties);

        String prefix = PropUtils.getScopedPropertyPrefix(this);
        String colorString;

        if (tenDegreeColor == null) {
            colorString = defaultTenDegreeColorString;
        } else {
            colorString = Integer.toHexString(tenDegreeColor.getRGB());
        }
        properties.put(prefix + TenDegreeColorProperty, colorString);

        if (fiveDegreeColor == null) {
            colorString = defaultFiveDegreeColorString;
        } else {
            colorString = Integer.toHexString(fiveDegreeColor.getRGB());
        }
        properties.put(prefix + FiveDegreeColorProperty, colorString);

        if (oneDegreeColor == null) {
            colorString = defaultOneDegreeColorString;
        } else {
            colorString = Integer.toHexString(oneDegreeColor.getRGB());
        }
        properties.put(prefix + OneDegreeColorProperty, colorString);
        
        properties.put(prefix + BelowOneDegreeColorProperty, colorString);
        
        if (equatorColor == null) {
            colorString = defaultEquatorColorString;
        } else {
            colorString = Integer.toHexString(equatorColor.getRGB());
        }
        properties.put(prefix + EquatorColorProperty, colorString);
        
        if (dateLineColor == null) {
            colorString = defaultDateLineColorString;
        } else {
            colorString = Integer.toHexString(dateLineColor.getRGB());
        }
        properties.put(prefix + DateLineColorProperty, colorString);
        
        if (specialLineColor == null) {
            colorString = defaultSpecialLineColorString;
        } else {
            colorString = Integer.toHexString(specialLineColor.getRGB());
        }
        properties.put(prefix + SpecialLineColorProperty, colorString);
        
        if (textColor == null) {
            colorString = defaultTextColorString;
        } else {
            colorString = Integer.toHexString(textColor.getRGB());
        }
        properties.put(prefix + TextColorProperty, colorString);
        
        properties.put(prefix + ThresholdProperty, Integer.toString(threshold));
        properties.put(prefix + FontSizeProperty, Integer.toString(fontSize)); //DNA

        properties.put(prefix + ShowOneAndFiveProperty, new Boolean(showOneAndFiveLines).toString());
        
        properties.put(prefix + ShowRulerProperty, new Boolean(showRuler).toString());

        return properties;
    }

    /**
     * The properties and prefix are managed and decoded here, for
     * the standard uses of the GraticuleLayer.
     *
     * _at_param properties the properties set in the properties file.
     */
    public Properties getPropertyInfo(Properties properties) {
        properties = super.getPropertyInfo(properties);
        String interString;
         properties.put(initPropertiesProperty, TenDegreeColorProperty + " " + FiveDegreeColorProperty + " " + OneDegreeColorProperty + " " + EquatorColorProperty + " " + DateLineColorProperty + " " + SpecialLineColorProperty + " " + ShowOneAndFiveProperty + " " + ShowRulerProperty + " " + ThresholdProperty + " " + FontSizeProperty);
    
        interString = i18n.get(GraticuleLayer.class,TenDegreeColorProperty,I18n.TOOLTIP,"Color of the ten degree graticule lines.");
        properties.put(TenDegreeColorProperty, interString);
        interString = i18n.get(GraticuleLayer.class,TenDegreeColorProperty,TenDegreeColorProperty);
        properties.put(TenDegreeColorProperty + LabelEditorProperty, interString);
        properties.put(TenDegreeColorProperty + ScopedEditorProperty,
                       "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor");

        interString = i18n.get(GraticuleLayer.class,FiveDegreeColorProperty,I18n.TOOLTIP,"Color of the five degree graticule lines.");
        properties.put(FiveDegreeColorProperty, interString);
        interString = i18n.get(GraticuleLayer.class,FiveDegreeColorProperty,"Color of the five degree graticule lines.");
        properties.put(FiveDegreeColorProperty + ScopedEditorProperty, interString);
        interString = i18n.get(GraticuleLayer.class,FiveDegreeColorProperty,FiveDegreeColorProperty);
        properties.put(FiveDegreeColorProperty + LabelEditorProperty, interString);
        properties.put(FiveDegreeColorProperty + ScopedEditorProperty,
                       "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor");

        interString = i18n.get(GraticuleLayer.class,OneDegreeColorProperty,I18n.TOOLTIP,"Color of the one degree graticule lines.");
        properties.put(OneDegreeColorProperty, interString);
        interString = i18n.get(GraticuleLayer.class,OneDegreeColorProperty,OneDegreeColorProperty);
        properties.put(OneDegreeColorProperty + LabelEditorProperty, interString);
        properties.put(OneDegreeColorProperty + ScopedEditorProperty,
                       "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor");

        interString = i18n.get(GraticuleLayer.class,EquatorColorProperty,I18n.TOOLTIP,"Color of the Equator.");
        properties.put(EquatorColorProperty,interString );
        interString = i18n.get(GraticuleLayer.class,EquatorColorProperty,EquatorColorProperty);
        properties.put(EquatorColorProperty + LabelEditorProperty, interString);
        properties.put(EquatorColorProperty + ScopedEditorProperty,
                       "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor");

        interString = i18n.get(GraticuleLayer.class,DateLineColorProperty,I18n.TOOLTIP,"Color of the Date line.");
        properties.put(DateLineColorProperty, interString);
        interString = i18n.get(GraticuleLayer.class,DateLineColorProperty,DateLineColorProperty);
        properties.put(DateLineColorProperty + LabelEditorProperty, interString);
        properties.put(DateLineColorProperty + ScopedEditorProperty,
                       "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor");

        interString = i18n.get(GraticuleLayer.class,SpecialLineColorProperty,I18n.TOOLTIP,"Color of Tropic of Cancer, Capricorn lines.");
        properties.put(SpecialLineColorProperty, interString);
        interString = i18n.get(GraticuleLayer.class,SpecialLineColorProperty,SpecialLineColorProperty);
        properties.put(SpecialLineColorProperty + LabelEditorProperty, interString);
        properties.put(SpecialLineColorProperty + ScopedEditorProperty,
                       "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor");

        interString = i18n.get(GraticuleLayer.class,TextColorProperty,I18n.TOOLTIP,"Color of the line label text.");
        properties.put(TextColorProperty, interString);
        interString = i18n.get(GraticuleLayer.class,TextColorProperty,TextColorProperty);
        properties.put(TextColorProperty + LabelEditorProperty, interString);
        properties.put(TextColorProperty + ScopedEditorProperty,
                       "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor");

        interString = i18n.get(GraticuleLayer.class,ThresholdProperty,I18n.TOOLTIP,"The number of lines showing before finer grain lines appear.");
        properties.put(ThresholdProperty, interString);
        interString = i18n.get(GraticuleLayer.class,ThresholdProperty,ThresholdProperty);
        properties.put(ThresholdProperty + LabelEditorProperty, interString);
    
        interString = i18n.get(GraticuleLayer.class,ShowOneAndFiveProperty,I18n.TOOLTIP,"Show the one and five degree lines.");
        properties.put(ShowOneAndFiveProperty, interString);
        interString = i18n.get(GraticuleLayer.class,ShowOneAndFiveProperty,ShowOneAndFiveProperty);
        properties.put(ShowOneAndFiveProperty + LabelEditorProperty, interString);
        properties.put(ShowOneAndFiveProperty + ScopedEditorProperty,
                       "com.bbn.openmap.util.propertyEditor.TrueFalsePropertyEditor");

        interString = i18n.get(GraticuleLayer.class,ShowRulerProperty,I18n.TOOLTIP,"Show the line label text.");
        properties.put(ShowRulerProperty, interString);
        interString = i18n.get(GraticuleLayer.class,ShowRulerProperty,ShowRulerProperty);
        properties.put(ShowRulerProperty + LabelEditorProperty, interString);
        properties.put(ShowRulerProperty + ScopedEditorProperty,
                       "com.bbn.openmap.util.propertyEditor.TrueFalsePropertyEditor");

        //DNA
        interString = i18n.get(GraticuleLayer.class,FontSizeProperty,I18n.TOOLTIP, "The size of the font, in points, of the line labels.");
        properties.put(FontSizeProperty, interString);
        interString = i18n.get(GraticuleLayer.class,FontSizeProperty,FontSizeProperty);
        properties.put(FontSizeProperty + LabelEditorProperty, interString);
        //DNA
        return properties;
    }

    /**
     * Implementing the ProjectionPainter interface.
     */
    public synchronized void renderDataForProjection(Projection proj, java.awt.Graphics g) {
        if (proj == null) {
            Debug.error("GraticuleLayer.renderDataForProjection: null projection!");
            return;
        } else if (!proj.equals(getProjection())) {
            setProjection(proj.makeClone());
            // Figure out which line type to use
            if (proj instanceof Cylindrical) boxy = true;
            else boxy = false;

            setList(constructGraticuleLines());
        }
        paint(g);
    }

    /**
     * Invoked when the projection has changed or this Layer has been added to
     * the MapBean.
     * <p>
     * Perform some extra checks to see if reprojection of the graphics is
     * really necessary.
     * _at_param e ProjectionEvent
     *
     */
    public void projectionChanged(ProjectionEvent e) {

        // extract the projection and check to see if it's really different.
        // if it isn't then we don't need to do all the work again, just
        // repaint.
        Projection proj = setProjection(e);
        if (proj == null) {
            repaint();
            return;
        }
        
        // Figure out which line type to use
        if (proj instanceof Cylindrical) boxy = true;
        else boxy = false;

        setList(null);
        doPrepare();
    }

    /**
     * Creates the OMGraphic list with graticule lines.
     */
    public synchronized OMGraphicList prepare() {
        return constructGraticuleLines();
    }

    /**
     * Create the graticule lines.
     * <p>
     * NOTES:
     * <ul>
     * <li>Currently graticule lines are hardcoded to 10 degree intervals.
     * <li>No thought has been given to clipping based on the view rectangle.
     * For non-boxy projections performance may be degraded at very large
     * scales. (But we make up for this by running the task in its own
     * thread to support liveness).
     * </ul>
     * _at_return OMGraphicList new graphic list
     */
    protected OMGraphicList constructGraticuleLines() {
        float[] llp;

        OMGraphicList newgraphics = new OMGraphicList(20);
        // Lets figure out which lines should be painted...
        Projection projection = getProjection();

        if (projection == null) {
            return newgraphics;
        }
        tenDegreeLines = null;
        
        if (showOneAndFiveLines || showRuler || showBelowOneLines) {

            float left = projection.getUpperLeft().getLongitude();
            float right = projection.getLowerRight().getLongitude();
            float up = projection.getUpperLeft().getLatitude();
            float down = projection.getLowerRight().getLatitude();

            if (up > 80.0f) up = 80.0f;
            if (down > 80.0f) down = 80f; // unlikely
            if (up < -80.0f) up = -80.0f; // unlikely
            if (down < -80) down = -80.0f;

            int showWhichLines = evaluateSpacing(up, down, left, right);

            // Find out whether we need to do one or two queries,
            // depending on if we're straddling the dateline.
            if ((left > 0 && right < 0) ||
                (left > right) ||
                (Math.abs(left - right) < 1)) {
                // Test to draw the ones and fives, which will also do
                // the labels.

                if (showWhichLines != SHOW_TENS) {
                    newgraphics.add(constructGraticuleLines(up, down, left, 180.0f, showWhichLines));
                    newgraphics.add(constructGraticuleLines(up, down, -180.0f, right, showWhichLines));
                } else if (showRuler) { // Just do the labels for the tens lines
                    newgraphics.add(constructTensLabels(up, down, left, 180.0f, true));
                    newgraphics.add(constructTensLabels(up, down, -180.0f, right, false));
                }
            } else {
                // Test to draw the ones and fives, which will also do
                // the labels.
                if (showWhichLines != SHOW_TENS) {
                    newgraphics = constructGraticuleLines(up, down, left, right, showWhichLines);
                } else if (showRuler) { // Just do the labels for the tens lines
                    newgraphics.add(constructTensLabels(up, down, left, right, true));
                }
            }
        }

        OMGraphicList list;
        if (tenDegreeLines == null) {
            list = constructTenDegreeLines();
            tenDegreeLines = list;
        } else {
            synchronized(tenDegreeLines) {
                setLineTypeAndProject(tenDegreeLines, boxy ? OMGraphic.LINETYPE_STRAIGHT : OMGraphic.LINETYPE_RHUMB);
            }
        }
        if (markerLines == null) {
            list = constructMarkerLines();
            markerLines = list;
        } else {
            synchronized(markerLines) {
                setLineTypeAndProject(markerLines, boxy ? OMGraphic.LINETYPE_STRAIGHT : OMGraphic.LINETYPE_RHUMB);
            }
        }

        newgraphics.add(markerLines);
        newgraphics.add(tenDegreeLines);

        if (Debug.debugging("graticule")) {
            Debug.output("GraticuleLayer.constructGraticuleLines(): " +
                         "constructed " + newgraphics.size() + " graticule lines");
        }

        return newgraphics;
    }

    /**
     * Figure out which graticule lines should be drawn based on the
     * treshold set in the layer, and the coordinates of the screen.
     * Method checks for crossing of the dateline, but still assumes
     * that the up and down latitude coordinates are less than
     * abs(+/-80). This is because the projection shouldn't give
     * anything above 90 degrees, and we limit the lines to less than
     * 80..
     *
     * _at_param up northern latitude corrdinate, in decimal degrees,
     * _at_param down southern latitude coordinate, in decimal degrees.
     * _at_param left western longitude coordinate, in decimal degrees,
     * _at_param right eastern longitude coordinate, in decimal degrees.
     * _at_return which lines should be shown, either SHOW_TENS,
     * SHOW_FIVES and SHOW_ONES.
     */
    protected int evaluateSpacing(float up, float down,
                                  float left, float right) {
        int ret = SHOW_TENS;

        // Set the flag for when labels are wanted, but not the 1 and
        // 5 lines;
        if (!showOneAndFiveLines && !showBelowOneLines) {
            return ret;
        }

        // Find the north - south difference
        float nsdiff = up - down;
        // And the east - west difference
        float ewdiff;
        // Check for straddling the dateline -west is positive while
        // right is negative, or, in a big picture view, the west is
        // positive, east is positive, and western hemisphere is
        // between them.
        if ((left > 0 && right < 0) ||
            (left > right) ||
            (Math.abs(left - right) < 1)) {
            ewdiff = (180.0f - left) + (right + 180.0f);
        } else {
            ewdiff = right - left;
        }

        // And use the lesser of the two.
        float diff = (nsdiff < ewdiff)?nsdiff:ewdiff;
        // number of 10 degree lines
        if ((diff/10) <= (float)threshold) ret = SHOW_FIVES;
        // number of five degree lines
        if ((diff/5) <= (float)threshold) ret = SHOW_ONES;

        return ret;
    }

    /**
     * Construct the five degree and one degree graticule lines,
     * depending on the showWhichLines setting. Assumes that the
     * coordinates passed in do not cross the dateline, and that the
     * up is not greater than 80 and that the south is not less than
     * -80.
     *
     * _at_param up northern latitude corrdinate, in decimal degrees,
     * _at_param down southern latitude coordinate, in decimal degrees.
     * _at_param left western longitude coordinate, in decimal degrees,
     * _at_param right eastern longitude coordinate, in decimal degrees.
     * _at_param showWhichLines indicator for which level of lines should
     * be included, either SHOW_FIVES or SHOW_ONES. SHOW_TENS could
     * be there, too, but then we wouldn't do anything.
     */
    protected OMGraphicList constructGraticuleLines(float up, float down,
                                                    float left, float right,
                                                    int showWhichLines) {
        OMGraphicList lines = new OMGraphicList();

        // Set the line limits for the lat/lon lines...
        int north = (int)Math.ceil(up);
        if (north > 80) north = 80;

        int south = (int)Math.floor(down);
        south -= (south%10); // Push down to the lowest 10 degree line.
        // for neg numbers, Mod raised it, lower it again. Also
        // handle straddling the equator.
        if ((south < 0 && south > -80) || south == 0) south -= 10;

        int west = (int)Math.floor(left);
        west -= (west%10);
        // for neg numbers, Mod raised it, lower it again. Also
        // handle straddling the prime meridian.
        if ((west < 0 && west > -180) || west == 0) west -= 10;

        int east = (int)Math.ceil(right);
        if (east > 180) east = 180;

        int stepSize;
        // Choose how far apart the lines will be.
        stepSize = ((showWhichLines == SHOW_ONES)? 1:5);
        float[] llp;
        OMPoly currentLine;
        OMText currentText;

        // For calculating text locations
        java.awt.Point point;
        LatLonPoint llpoint;

        Projection projection = getProjection();

        // generate other parallels of latitude be creating series
        // of polylines
        for (int i = south; i < north; i += stepSize) {
            float lat = (float)i;
            // generate parallel of latitude North/South of the equator
            if (west < 0 && east > 0) {
                llp = new float[6];
                llp[2] = lat; llp[3] = 0f;
                llp[4] = lat; llp[5] = east;
            } else {
                llp = new float[4];
                llp[2] = lat; llp[3] = east;
            }
            llp[0] = lat; llp[1] = west;

            // Do not duplicate the 10 degree line.
            if ((lat%10) != 0) {
                currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES,
                                         boxy ? OMGraphic.LINETYPE_STRAIGHT
                                         : OMGraphic.LINETYPE_RHUMB);
                if ((lat%5) == 0) {
                    currentLine.setLinePaint(fiveDegreeColor);
                } else {
                    currentLine.setLinePaint(oneDegreeColor);
                }
                lines.addOMGraphic(currentLine);
            }

            if (showRuler && (lat%2) == 0) {
                if (boxy) {
                    point = projection.forward(lat, west);
                    point.x = 0;
                    llpoint = projection.inverse(point);
                } else {
                    llpoint = new LatLonPoint(lat, west);
                    while (projection.forward(llpoint).x < 0) {
                        llpoint.setLongitude(llpoint.getLongitude() + stepSize);
                    }
                }
                
                currentText = new OMText(llpoint.getLatitude(),
                                         llpoint.getLongitude(),
                                         // Move them up a little
                                         (int)2, (int) -2,
                                         Integer.toString((int)lat),
                                         font, OMText.JUSTIFY_LEFT);
                currentText.setLinePaint(textColor);
                lines.addOMGraphic(currentText);
            }
        }
        
        // generate lines of longitude
        for (int i = west; i < east; i += stepSize) {
            float lon = (float)i;
            
            if (north < 0 && south > 0) {
                llp = new float[6];
                llp[2] = 0f; llp[3] = lon;
                llp[4] = south; llp[5] = lon;
            } else {
                llp = new float[4];
                llp[2] = south; llp[3] = lon;
            }
            llp[0] = north; llp[1] = lon;
            
            if ((lon%10) != 0) {
                currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES,
                                         boxy ? OMGraphic.LINETYPE_STRAIGHT
                                         : OMGraphic.LINETYPE_GREATCIRCLE);
                if ((lon%5) == 0) {
                    currentLine.setLinePaint(fiveDegreeColor);
                } else {
                    currentLine.setLinePaint(oneDegreeColor);
                }
                lines.addOMGraphic(currentLine);
            }

            if (showRuler && (lon%2) == 0) {
                if (boxy) {
                    point = projection.forward(south, lon);
                    point.y = projection.getHeight();
                    llpoint = projection.inverse(point);
                } else {
                    llpoint = new LatLonPoint(south, lon);
                    while (projection.forward(llpoint).y > projection.getHeight()) {
                        llpoint.setLatitude(llpoint.getLatitude() + stepSize);
                    }
                }
                
                currentText = new OMText(llpoint.getLatitude(),
                                         llpoint.getLongitude(),
                                         // Move them up a little
                                         (int)2, (int) -5,
                                         Integer.toString((int)lon),
                                         font, OMText.JUSTIFY_CENTER);
                currentText.setLinePaint(textColor);
                lines.addOMGraphic(currentText);

            }
        }

        if (Debug.debugging("graticule")) {
            Debug.output("GraticuleLayer.constructTenDegreeLines(): " +
                         "constructed " + lines.size() + " graticule lines");
        }
        lines.generate(projection);
        return lines;
    }

    /** Create the ten degree lines. */
    protected OMGraphicList constructTenDegreeLines() {

        OMGraphicList lines = new OMGraphicList(3);
        OMPoly currentLine;

        // generate other parallels of latitude by creating series
        // of polylines
        for (int i = 1; i <= 8; i++) {
            for (int j = -1; j < 2; j += 2) {
                float lat = (float)(10*i*j);
                // generate parallel of latitude North/South of the equator
                float[] llp = {lat, -180f, lat, -90f,
                               lat, 0f, lat, 90f, lat, 180f};
                currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES,
                                           boxy ? OMGraphic.LINETYPE_STRAIGHT
                                           : OMGraphic.LINETYPE_RHUMB);
                currentLine.setLinePaint(tenDegreeColor);
                lines.addOMGraphic(currentLine);
            }
        }

        // generate lines of longitude
        for (int i = 1; i < 18; i++) {
            for (int j = -1; j < 2; j += 2) {
                float lon = (float)(10*i*j);
                //not quite 90.0 for beautification reasons.
                float[] llp = {80f, lon,
                               0f, lon,
                               -80f, lon};
                if (MoreMath.approximately_equal(Math.abs(lon), 90f, 0.001f)) {
                    llp[0] = 90f;
                    llp[4] = -90f;
                }
                currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES,
                                         boxy ? OMGraphic.LINETYPE_STRAIGHT
                                         : OMGraphic.LINETYPE_GREATCIRCLE);
                currentLine.setLinePaint(tenDegreeColor);
                lines.addOMGraphic(currentLine);
            }
        }

        if (Debug.debugging("graticule")) {
            Debug.output("GraticuleLayer.constructTenDegreeLines(): " +
                         "constructed " + lines.size() + " graticule lines");
        }
        lines.generate(getProjection());
        return lines;
    }

    /**
     * Constructs the labels for the tens lines. Called from within
     * the constructGraticuleLines if the showRuler variable is true.
     * Usually called only if the ones and fives lines are not being
     * drawn.
     *
     * _at_param up northern latitude corrdinate, in decimal degrees,
     * _at_param down southern latitude coordinate, in decimal degrees.
     * _at_param left western longitude coordinate, in decimal degrees,
     * _at_param right eastern longitude coordinate, in decimal degrees.
     * _at_param doLats do the latitude labels if true.
     * _at_return OMGraphicList of labels.
     */
    protected OMGraphicList constructTensLabels(float up, float down,
                                                float left, float right,
                                                boolean doLats) {

        OMGraphicList labels = new OMGraphicList();

        // Set the line limits for the lat/lon lines...
        int north = (int)Math.ceil(up);
        if (north > 80) north = 80;

        int south = (int)Math.floor(down);
        south -= (south%10); // Push down to the lowest 10 degree line.
        // for neg numbers, Mod raised it, lower it again
        if ((south < 0 && south > -70) || south == 0) {
            south -= 10;
        }

        int west = (int)Math.floor(left);
        west -= (west%10);
        // for neg numbers, Mod raised it, lower it again
        if ((west < 0 && west > -170) || west == 0) {
            west -= 10;
        }

        int east = (int)Math.ceil(right);
        if (east > 180) east = 180;

        int stepSize = 10;
        OMText currentText;

        // For calculating text locations
        java.awt.Point point;
        LatLonPoint llpoint;
        Projection projection = getProjection();

        if (doLats) {

            // generate other parallels of latitude be creating series
            // of labels
            for (int i = south; i < north; i += stepSize) {
                float lat = (float)i;
                
                if ((lat%2) == 0) {
                    if (boxy) {
                        point = projection.forward(lat, west);
                        point.x = 0;
                        llpoint = projection.inverse(point);
                    } else {
                        llpoint = new LatLonPoint(lat, west);
                        while (projection.forward(llpoint).x < 0) {
                            llpoint.setLongitude(llpoint.getLongitude() + stepSize);
                        }
                    }
                    
                    currentText = new OMText(llpoint.getLatitude(), llpoint.getLongitude(),
                                             (int)2, (int) -2, // Move them up a little
                                             Integer.toString((int)lat),
                                             font, OMText.JUSTIFY_LEFT);
                    currentText.setLinePaint(textColor);
                    labels.addOMGraphic(currentText);
                }
            }
        }
        
        // generate labels of longitude
        for (int i = west; i < east; i += stepSize) {
            float lon = (float)i;
            
            if ((lon%2) == 0) {
                if (boxy) {
                    point = projection.forward(south, lon);
                    point.y = projection.getHeight();
                    llpoint = projection.inverse(point);
                } else {
                    llpoint = new LatLonPoint(south, lon);
                    while (projection.forward(llpoint).y > projection.getHeight()) {
                        llpoint.setLatitude(llpoint.getLatitude() + stepSize);
                    }
                }
                
                currentText = new OMText(llpoint.getLatitude(),
                                         llpoint.getLongitude(),
                                         // Move them up a little
                                         (int)2, (int) -5,
                                         Integer.toString((int)lon),
                                         font, OMText.JUSTIFY_CENTER);
                currentText.setLinePaint(textColor);
                labels.addOMGraphic(currentText);

            }
        }

        if (Debug.debugging("graticule")) {
            Debug.output("GraticuleLayer.constructTensLabels(): " +
                         "constructed " + labels.size() + " graticule labels");
        }
        labels.generate(projection);
        return labels;
    }

    /** Constructs the Dateline and Prime Meridian lines. */
    protected OMGraphicList constructMarkerLines() {

        OMGraphicList lines = new OMGraphicList(3);
        OMPoly currentLine;

        // generate Prime Meridian and Dateline
        for (int j = 0; j < 360; j += 180) {
            float lon = (float)j;
            float[] llp = {90f, lon,
                           0f, lon,
                           -90f, lon};
            currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES,
                                     boxy ? OMGraphic.LINETYPE_STRAIGHT
                                     : OMGraphic.LINETYPE_GREATCIRCLE);
            currentLine.setLinePaint(dateLineColor);
            lines.addOMGraphic(currentLine);
        }

        // equator
        float[] llp = {0f, -180f,
                       0f, -90f,
                       0f, 0f,
                       0f, 90f,
                       0f, 180f};
        // polyline
        currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES,
                                 boxy ? OMGraphic.LINETYPE_STRAIGHT
                                 : OMGraphic.LINETYPE_GREATCIRCLE);
        currentLine.setLinePaint(equatorColor);
        lines.addOMGraphic(currentLine);

        if (Debug.debugging("graticule")) {
            Debug.output("GraticuleLayer.constructMarkerLines(): " +
                         "constructed " + lines.size() + " graticule lines");
        }
        lines.generate(getProjection());
        return lines;
    }

    /**
     * Take a graphic list, and set all the items on the list to the
     * line type specified, and project them into the current
     * projection.
     *
     * _at_param list the list containing the lines to change.
     * _at_param lineType the line type to cahnge the lines to. */
    protected void setLineTypeAndProject(OMGraphicList list, int lineType) {
        int size = list.size();
        OMGraphic graphic;
        for (int i = 0; i < size; i++) {
            graphic = list.getOMGraphicAt(i);
            graphic.setLineType(lineType);
            graphic.generate(getProjection());
        }
    }


    //----------------------------------------------------------------------
    // GUI
    //----------------------------------------------------------------------

    /** The user interface palette for the DTED layer. */
    protected Box palette = null;

    /** Creates the interface palette. */
    public java.awt.Component getGUI() {

        if (palette == null) {
            if (Debug.debugging("graticule"))
                Debug.output("GraticuleLayer: creating Graticule Palette.");

            palette = Box.createVerticalBox();

            JPanel layerPanel = PaletteHelper.createPaletteJPanel(i18n.get(GraticuleLayer.class,"layerPanel","Graticule Layer Options"));
            
            ActionListener al = new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        String ac = e.getActionCommand();
                    
                        if (ac.equalsIgnoreCase(ShowRulerProperty)) {
                            JCheckBox jcb = (JCheckBox)e.getSource();
                            showRuler = jcb.isSelected();
                        } else if (ac.equalsIgnoreCase(ShowOneAndFiveProperty)) {
                            JCheckBox jcb = (JCheckBox)e.getSource();
                            showOneAndFiveLines = jcb.isSelected();
                        } else {
                            Debug.error("Unknown action command \"" + ac +
                                        "\" in GraticuleLayer.actionPerformed().");
                        }
                    }
                };

            showRulerButton = new JCheckBox(i18n.get(GraticuleLayer.class,"showRulerButton","Show Lat/Lon Labels"), showRuler);
            showRulerButton.addActionListener(al);
            showRulerButton.setActionCommand(ShowRulerProperty);

            show15Button = new JCheckBox(i18n.get(GraticuleLayer.class,"show15Button","Show 1, 5 Degree Lines"), showOneAndFiveLines);
            show15Button.addActionListener(al);
            show15Button.setActionCommand(ShowOneAndFiveProperty);


            layerPanel.add(showRulerButton);
            layerPanel.add(show15Button);
            palette.add(layerPanel);
            
            JPanel subbox3 = new JPanel(new GridLayout(0, 1));
            
            JButton setProperties = new JButton(i18n.get(GraticuleLayer.class,"setProperties","Preferences"));
            setProperties.setActionCommand(DisplayPropertiesCmd);
            setProperties.addActionListener(this);
            subbox3.add(setProperties);

            JButton redraw = new JButton(i18n.get(GraticuleLayer.class,"redraw","Redraw Graticule Layer"));
            redraw.setActionCommand(RedrawCmd);
            redraw.addActionListener(this);
            subbox3.add(redraw);
            palette.add(subbox3);
        }
        return palette;
    }

    //----------------------------------------------------------------------
    // ActionListener interface implementation
    //----------------------------------------------------------------------
    
    /**
     * Used just for the redraw button.
     */
    public void actionPerformed(ActionEvent e) {
        super.actionPerformed(e);
        String command = e.getActionCommand();

        if (command == RedrawCmd) {
            //redrawbutton
            if (isVisible()) {
                doPrepare();
            }
        }
    }

}


// **********************************************************************
//
// <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 RemoveableProperty = "removeable";
    
    /**
     * Flag to designate the layer as removeable or not.
     */
    protected boolean removeable = 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 setRemoveable(boolean set) {
        this.removeable = set;
        Component comp = getComponent();
        if ((comp != null) && (comp instanceof Layer)) {
            ((Layer)comp).setRemoveable(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 isRemoveable() {
        Component comp = getComponent();
        if ((comp != null) && (comp instanceof Layer)) {
            this.removeable = ((Layer)comp).isRemoveable();
        }
        return removeable;
    }
    
    ////// 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));
        setRemoveable(PropUtils.booleanFromProperties(setList, realPrefix + RemoveableProperty, removeable));
    }
    
    /**
     * 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 + RemoveableProperty, new Boolean(removeable).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, RemoveableProperty, I18n.TOOLTIP, "Flag to allow layer to be deleted.");
        list.put(RemoveableProperty, internString);
        internString = i18n.get(Layer.class, RemoveableProperty, "Removeable");
        list.put(RemoveableProperty + LabelEditorProperty, internString);
        list.put(RemoveableProperty + 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.
     *
     * _at_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.
     * _at_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
     * _at_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/proj/ProjMath.java,v $
// $RCSfile: ProjMath.java,v $
// $Revision: 1.4 $
// $Date: 2004/10/14 18:18:22 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.proj;

import com.bbn.openmap.MoreMath;

/**
 * Math functions used by projection code.
 */
public final class ProjMath {

    /**
     * North pole latitude in radians.
     */
    public final static transient float NORTH_POLE_F = MoreMath.HALF_PI;

    /**
     * South pole latitude in radians.
     */
    public final static transient float SOUTH_POLE_F = -NORTH_POLE_F;

    /**
     * North pole latitude in radians.
     */
    public final static transient double NORTH_POLE_D = MoreMath.HALF_PI_D;

    /**
     * South pole latitude in radians.
     */
    public final static transient double SOUTH_POLE_D = -NORTH_POLE_D;

    /**
     * Dateline longitude in radians.
     */
    public final static transient float DATELINE_F = (float) Math.PI;

    /**
     * Dateline longitude in radians.
     */
    public final static transient double DATELINE_D = Math.PI;

    /**
     * Longitude range in radians.
     */
    public final static transient float LON_RANGE_F = (float) MoreMath.TWO_PI;

    /**
     * Longitude range in radians.
     */
    public final static transient double LON_RANGE_D = MoreMath.TWO_PI_D;

    // cannot construct
    private ProjMath() {}

    /**
     * rounds the quantity away from 0.
     *
     * _at_param x in value
     * _at_return double
     * _at_see #qint(double)
     */
    final public static double roundAdjust(double x) {
        return qint_old(x);
    }

    /**
     * Rounds the quantity away from 0.
     *
     * _at_param x value
     * _at_return double
     */
    final public static double qint(double x) {
        return qint_new(x);
    }

    final private static double qint_old(double x) {
        return (((int) x) < 0) ? (x - 0.5) : (x + 0.5);
    }

    final private static double qint_new(double x) {
        // -1 or +1 away from zero
        return (x <= 0.0) ? (x - 1.0) : (x + 1.0);
    }

    /**
     * Calculate the shortest arc distance between two lons.
     *
     * _at_param lon1 radians
     * _at_param lon2 radians
     * _at_return float distance
     */
    final public static float lonDistance(float lon1, float lon2) {
        return (float) Math.min(Math.abs(lon1 - lon2), ((lon1 < 0) ? lon1
                + Math.PI : Math.PI - lon1)
                + ((lon2 < 0) ? lon2 + Math.PI : Math.PI - lon2));
    }

    /**
     * Convert between decimal degrees and scoords.
     *
     * _at_param deg degrees
     * _at_return long scoords
     *
     */
    final public static long DEG_TO_SC(double deg) {
        return (long) (deg * 3600000);
    }

    /**
     * Convert between decimal degrees and scoords.
     *
     * _at_param sc scoords
     * _at_return double decimal degrees
     */
    final public static double SC_TO_DEG(int sc) {
        return ((double) (sc) / (60.0 * 60.0 * 1000.0));
    }

    /**
     * Convert radians to degrees.
     *
     * _at_param rad radians
     * _at_return double decimal degrees
     */
    final public static double radToDeg(double rad) {
        return (rad * (180.0d / Math.PI));
    }

    /**
     * Convert radians to degrees.
     *
     * _at_param rad radians
     * _at_return float decimal degrees
     */
    final public static float radToDeg(float rad) {
        return (float)radToDeg((double)rad);
    }

    /**
     * Convert degrees to radians.
     *
     * _at_param deg degrees
     * _at_return double radians
     */
    final public static double degToRad(double deg) {
        return (deg * (Math.PI / 180.0d));
    }

    /**
     * Convert degrees to radians.
     *
     * _at_param deg degrees
     * _at_return float radians
     */
    final public static float degToRad(float deg) {
        return (float)degToRad((double)deg);
    }

    /**
     * Generate a hashCode value for a lat/lon pair.
     *
     * _at_param lat latitude
     * _at_param lon longitude
     * _at_return int hashcode
     *
     */
    final public static int hashLatLon(float lat, float lon) {
        if (lat == -0f)
            lat = 0f;//handle negative zero (anything else?)
        if (lon == -0f)
            lon = 0f;
        int tmp = Float.floatToIntBits(lat);
        int hash = (tmp << 5) | (tmp >> 27);//rotate the lat bits
        return hash ^ Float.floatToIntBits(lon);//XOR with lon
    }

    /**
     * Converts an array of decimal degrees float lat/lons to float
     * radians in place.
     *
     * _at_param degs float[] lat/lons in decimal degrees
     * _at_return float[] lat/lons in radians
     */
    final public static float[] arrayDegToRad(float[] degs) {
        for (int i = 0; i < degs.length; i++) {
            degs[i] = degToRad(degs[i]);
        }
        return degs;
    }

    /**
     * Converts an array of radian float lat/lons to decimal degrees
     * in place.
     *
     * _at_param rads float[] lat/lons in radians
     * _at_return float[] lat/lons in decimal degrees
     */
    final public static float[] arrayRadToDeg(float[] rads) {
        for (int i = 0; i < rads.length; i++) {
            rads[i] = radToDeg(rads[i]);
        }
        return rads;
    }

    /**
     * Converts an array of decimal degrees double lat/lons to double
     * radians in place.
     *
     * _at_param degs double[] lat/lons in decimal degrees
     * _at_return double[] lat/lons in radians
     */
    final public static double[] arrayDegToRad(double[] degs) {
        for (int i = 0; i < degs.length; i++) {
            degs[i] = degToRad(degs[i]);
        }
        return degs;
    }

    /**
     * Converts an array of radian double lat/lons to decimal degrees
     * in place.
     *
     * _at_param rads double[] lat/lons in radians
     * _at_return double[] lat/lons in decimal degrees
     */
    final public static double[] arrayRadToDeg(double[] rads) {
        for (int i = 0; i < rads.length; i++) {
            rads[i] = radToDeg(rads[i]);
        }
        return rads;
    }

    /**
     * Normalizes radian latitude. Normalizes latitude if at or
     * exceeds epsilon distance from a pole.
     *
     * _at_param lat float latitude in radians
     * _at_param epsilon epsilon (&gt;= 0) radians distance from pole
     * _at_return float latitude (-PI/2 &lt;= phi &lt;= PI/2)
     * _at_see Proj#normalize_latitude(float)
     * _at_see com.bbn.openmap.LatLonPoint#normalize_latitude(float)
     */
    public final static float normalize_latitude(float lat, float epsilon) {
        if (lat > NORTH_POLE_F - epsilon) {
            return NORTH_POLE_F - epsilon;
        } else if (lat < SOUTH_POLE_F + epsilon) {
            return SOUTH_POLE_F + epsilon;
        }
        return lat;
    }

    /**
     * Normalizes radian latitude. Normalizes latitude if at or
     * exceeds epsilon distance from a pole.
     *
     * _at_param lat double latitude in radians
     * _at_param epsilon epsilon (&gt;= 0) radians distance from pole
     * _at_return double latitude (-PI/2 &lt;= phi &lt;= PI/2)
     * _at_see Proj#normalize_latitude(float)
     * _at_see com.bbn.openmap.LatLonPoint#normalize_latitude(float)
     */
    public final static double normalize_latitude(double lat, double epsilon) {
        if (lat > NORTH_POLE_D - epsilon) {
            return NORTH_POLE_D - epsilon;
        } else if (lat < SOUTH_POLE_D + epsilon) {
            return SOUTH_POLE_D + epsilon;
        }
        return lat;
    }

    /**
     * Sets radian longitude to something sane.
     *
     * _at_param lon float longitude in radians
     * _at_return float longitude (-PI &lt;= lambda &lt; PI)
     * _at_see com.bbn.openmap.LatLonPoint#wrap_longitude(float)
     */
    public final static float wrap_longitude(float lon) {
        if ((lon < -DATELINE_F) || (lon > DATELINE_F)) {
            lon += DATELINE_F;
            lon = (lon % LON_RANGE_F);
            lon += (lon < 0) ? DATELINE_F : -DATELINE_F;
        }
        return lon;
    }

    /**
     * Sets radian longitude to something sane.
     *
     * _at_param lon double longitude in radians
     * _at_return double longitude (-PI &lt;= lambda &lt; PI)
     * _at_see #wrap_longitude(float)
     */
    public final static double wrap_longitude(double lon) {
        if ((lon < -DATELINE_D) || (lon > DATELINE_D)) {
            lon += DATELINE_D;
            lon = (lon % LON_RANGE_D);
            lon += (lon < 0) ? DATELINE_D : -DATELINE_D;
        }
        return lon;
    }

    /**
     * Converts units (km, nm, miles, etc) to decimal degrees for a
     * spherical planet. This does not check for arc distances &gt;
     * 1/2 planet circumference, which are better represented as (2pi -
     * calculated arc).
     *
     * _at_param u units float value
     * _at_param uCircumference units circumference of planet
     * _at_return float decimal degrees
     */
    final public static float sphericalUnitsToDeg(float u, float uCircumference) {
        return 360f * (u / uCircumference);
    }

    /**
     * Converts units (km, nm, miles, etc) to arc radians for a
     * spherical planet. This does not check for arc distances &gt;
     * 1/2 planet circumference, which are better represented as (2pi -
     * calculated arc).
     *
     * _at_param u units float value
     * _at_param uCircumference units circumference of planet
     * _at_return float arc radians
     */
    final public static float sphericalUnitsToRad(float u, float uCircumference) {
        return MoreMath.TWO_PI * (u / uCircumference);
    }

    /**
     * Calculate the geocentric latitude given a geographic latitude.
     * According to John Synder: <br>
     * "The geographic or geodetic latitude is the angle which a line
     * perpendicular to the surface of the ellipsoid at the given
     * point makes with the plane of the equator. ...The geocentric
     * latitude is the angle made by a line to the center of the
     * ellipsoid with the equatorial plane". ( <i>Map Projections --A
     * Working Manual </i>, p 13)
     * <p>
     * Translated from Ken Anderson's lisp code <i>Freeing the Essence
     * of Computation </i>
     *
     * _at_param lat float geographic latitude in radians
     * _at_param flat float flatening factor
     * _at_return float geocentric latitude in radians
     * _at_see #geographic_latitude
     */
    public final static float geocentric_latitude(float lat, float flat) {
        float f = 1.0f - flat;
        return (float) Math.atan((f * f) * (float) Math.tan(lat));
    }

    /**
     * Calculate the geographic latitude given a geocentric latitude.
     * Translated from Ken Anderson's lisp code <i>Freeing the Essence
     * of Computation </i>
     *
     * _at_param lat float geocentric latitude in radians
     * _at_param flat float flatening factor
     * _at_return float geographic latitude in radians
     * _at_see #geocentric_latitude
     */
    public final static float geographic_latitude(float lat, float flat) {
        float f = 1.0f - flat;
        return (float) Math.atan((float) Math.tan(lat) / (f * f));
    }

    /**
     * Given a couple of points representing a bounding box, find out
     * what the scale should be in order to make those points appear
     * at the corners of the projection.
     *
     * _at_param ll1 the upper left coordinates of the bounding box.
     * _at_param ll2 the lower right coordinates of the bounding box.
     * _at_param projection the projection to use for other projection
     * parameters, like map width and map height.
     */
    public static float getScale(com.bbn.openmap.LatLonPoint ll1,
                                 com.bbn.openmap.LatLonPoint ll2,
                                 Projection projection) {
        if (projection == null) {
            return Float.MAX_VALUE;
        }

        java.awt.Point point1 = projection.forward(ll1);
        java.awt.Point point2 = projection.forward(ll2);

        return getScale(ll1, ll2, point1, point2, projection);
    }

    /**
     * Given a couple of points representing a bounding box, find out
     * what the scale should be in order to make those points appear
     * at the corners of the projection.
     *
     * _at_param point1 a java.awt.Point reflecting a pixel spot on the
     * projection, usually the upper left corner of the area of
     * interest.
     * _at_param point2 a java.awt.Point reflecting a pixel spot on the
     * projection, usually the lower right corner of the area
     * of interest.
     * _at_param projection the projection to use for other projection
     * parameters, like map width and map height.
     */
    public static float getScale(java.awt.Point point1, java.awt.Point point2,
                                 Projection projection) {

        if (projection == null) {
            return Float.MAX_VALUE;
        }

        com.bbn.openmap.LatLonPoint ll1 = projection.inverse(point1);
        com.bbn.openmap.LatLonPoint ll2 = projection.inverse(point2);

        return getScale(ll1, ll2, point1, point2, projection);
    }

    /**
     * Given a couple of points representing a bounding box, find out
     * what the scale should be in order to make those points appear
     * at the corners of the projection.
     *
     * _at_param ll1 the upper left coordinates of the bounding box.
     * _at_param ll2 the lower right coordinates of the bounding box.
     * _at_param point1 a java.awt.Point reflecting a pixel spot on the
     * projection that matches the ll1 coordinate, the upper
     * left corner of the area of interest.
     * _at_param point2 a java.awt.Point reflecting a pixel spot on the
     * projection that matches the ll2 coordinate, usually the
     * lower right corner of the area of interest.
     * _at_param projection the projection to use to query to get the
     * scale for, for projection type and height and width.
     */
    protected static float getScale(com.bbn.openmap.LatLonPoint ll1,
                                    com.bbn.openmap.LatLonPoint ll2,
                                    java.awt.Point point1,
                                    java.awt.Point point2, Projection projection) {

        return projection.getScale(ll1, ll2, point1, point2);
    }

    /*
     * public static void main(String[] args) { float degs =
     * sphericalUnitsToRad( Planet.earthEquatorialRadius/2,
     * Planet.earthEquatorialRadius); Debug.output("degs = " + degs);
     * float LAT_DEC_RANGE = 90.0f; float LON_DEC_RANGE = 360.0f;
     * float lat, lon; for (int i = 0; i < 100; i++) { lat =
     * com.bbn.openmap.LatLonPoint.normalize_latitude(
     * (float)Math.random()*LAT_DEC_RANGE); lon =
     * com.bbn.openmap.LatLonPoint.wrap_longitude(
     * (float)Math.random()*LON_DEC_RANGE); Debug.output( "(" + lat +
     * "," + lon + ") : (" + degToRad(lat) + "," + degToRad(lon) + ") : (" +
     * radToDeg(degToRad(lat)) + "," + radToDeg(degToRad(lon)) + ")"); } }
     */
}

// **********************************************************************
//
// <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/proj/coords/MGRSPoint.java,v $
// $RCSfile: MGRSPoint.java,v $
// $Revision: 1.8 $
// $Date: 2004/10/14 18:18:22 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.proj.coords;


import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintStream;

import com.bbn.openmap.LatLonPoint;
import com.bbn.openmap.proj.Ellipsoid;
import com.bbn.openmap.util.ArgParser;
import com.bbn.openmap.util.Debug;

/**
 * A class representing a MGRS coordinate that has the ability to
 * provide the decimal degree lat/lon equivalent, as well as the UTM
 * equivalent. This class does not do checks to see if the MGRS
 * coordiantes provided actually make sense. It assumes that the
 * values are valid.
 */
public class MGRSPoint extends UTMPoint {

    /**
     * UTM zones are grouped, and assigned to one of a group of 6
     * sets.
     */
    protected final static int NUM_100K_SETS = 6;
    /**
     * The column letters (for easting) of the lower left value, per
     * set.
     */
    public final static int[] SET_ORIGIN_COLUMN_LETTERS = { 'A', 'J', 'S', 'A',
            'J', 'S' };
    /**
     * The row letters (for northing) of the lower left value, per
     * set.
     */
    public final static int[] SET_ORIGIN_ROW_LETTERS = { 'A', 'F', 'A', 'F',
            'A', 'F' };
    /**
     * The column letters (for easting) of the lower left value, per
     * set,, for Bessel Ellipsoid.
     */
    public final static int[] BESSEL_SET_ORIGIN_COLUMN_LETTERS = { 'A', 'J',
            'S', 'A', 'J', 'S' };
    /**
     * The row letters (for northing) of the lower left value, per
     * set, for Bessel Ellipsoid.
     */
    public final static int[] BESSEL_SET_ORIGIN_ROW_LETTERS = { 'L', 'R', 'L',
            'R', 'L', 'R' };

    public final static int SET_NORTHING_ROLLOVER = 20000000;
    /**
     * Use 5 digits for northing and easting values, for 1 meter
     * accuracy of coordinate.
     */
    public final static int ACCURACY_1_METER = 5;
    /**
     * Use 4 digits for northing and easting values, for 10 meter
     * accuracy of coordinate.
     */
    public final static int ACCURACY_10_METER = 4;
    /**
     * Use 3 digits for northing and easting values, for 100 meter
     * accuracy of coordinate.
     */
    public final static int ACCURACY_100_METER = 3;
    /**
     * Use 2 digits for northing and easting values, for 1000 meter
     * accuracy of coordinate.
     */
    public final static int ACCURACY_1000_METER = 2;
    /**
     * Use 1 digits for northing and easting values, for 10000 meter
     * accuracy of coordinate.
     */
    public final static int ACCURACY_10000_METER = 1;

    /** The set origin column letters to use. */
    protected int[] originColumnLetters = SET_ORIGIN_COLUMN_LETTERS;
    /** The set origin row letters to use. */
    protected int[] originRowLetters = SET_ORIGIN_ROW_LETTERS;

    public final static int A = 'A';
    public final static int I = 'I';
    public final static int O = 'O';
    public final static int V = 'V';
    public final static int Z = 'Z';

    protected boolean DEBUG = false;

    /** The String holding the MGRS coordinate value. */
    protected String mgrs;

    /**
     * Controls the number of digits that the MGRS coordinate will
     * have, which directly affects the accuracy of the coordinate.
     * Default is ACCURACY_1_METER, which indicates that MGRS
     * coordinates will have 10 digits (5 easting, 5 northing) after
     * the 100k two letter code, indicating 1 meter resolution.
     */
    protected int accuracy = ACCURACY_1_METER;

    /**
     * Point to create if you are going to use the static methods to
     * fill the values in.
     */
    public MGRSPoint() {
        DEBUG = Debug.debugging("mgrs");
    }

    /**
     * Constructs a new MGRS instance from a MGRS String.
     */
    public MGRSPoint(String mgrsString) {
        this();
        setMGRS(mgrsString);
    }

    /**
     * Contructs a new MGRSPoint instance from values in another
     * MGRSPoint.
     */
    public MGRSPoint(MGRSPoint point) {
        this();
        mgrs = point.mgrs;
        northing = point.northing;
        easting = point.easting;
        zone_number = point.zone_number;
        zone_letter = point.zone_letter;
        accuracy = point.accuracy;
    }

    /**
     * Create a MGRSPoint from UTM values;
     */
    public MGRSPoint(float northing, float easting, int zoneNumber,
            char zoneLetter) {
        super(northing, easting, zoneNumber, zoneLetter);
    }

    /**
     * Contruct a MGRSPoint from a LatLonPoint, assuming a WGS_84
     * ellipsoid.
     */
    public MGRSPoint(LatLonPoint llpoint) {
        this(llpoint, Ellipsoid.WGS_84);
    }

    /**
     * Construct a MGRSPoint from a LatLonPoint and a particular
     * ellipsoid.
     */
    public MGRSPoint(LatLonPoint llpoint, Ellipsoid ellip) {
        this();
        LLtoMGRS(llpoint, ellip, this);
    }

    /**
     * Set the MGRS value for this Point. Will be decoded, and the UTM
     * values figured out. You can call toLatLonPoint() to translate
     * it to lat/lon decimal degrees.
     */
    public void setMGRS(String mgrsString) {
        try {
            mgrs = mgrsString.toUpperCase(); // Just to make sure.
            decode(mgrs);
        } catch (StringIndexOutOfBoundsException sioobe) {
            throw new NumberFormatException("MGRSPoint has bad string: "
                    + mgrsString);
        } catch (NullPointerException npe) {
            // Blow off
        }
    }

    /**
     * Get the MGRS string value - the honkin' coordinate value.
     */
    public String getMGRS() {
        if (mgrs == null) {
            resolve();
        }
        return mgrs;
    }

    /**
     * Convert this MGRSPoint to a LatLonPoint, and assume a WGS_84
     * ellisoid.
     */
    public LatLonPoint toLatLonPoint() {
        return toLatLonPoint(Ellipsoid.WGS_84, new LatLonPoint());
    }

    /**
     * Convert this MGRSPoint to a LatLonPoint, and use the given
     * ellipsoid.
     */
    public LatLonPoint toLatLonPoint(Ellipsoid ellip) {
        return toLatLonPoint(ellip, new LatLonPoint());
    }

    /**
     * Fill in the given LatLonPoint with the converted values of this
     * MGRSPoint, and use the given ellipsoid.
     */
    public LatLonPoint toLatLonPoint(Ellipsoid ellip, LatLonPoint llpoint) {
        return MGRStoLL(this, ellip, llpoint);
    }

    /**
     * Returns a string representation of the object.
     *
     * _at_return String representation
     */
    public String toString() {
        return "MGRSPoint[" + mgrs + "]";
    }

    /**
     * Create a LatLonPoint from a MGRSPoint.
     *
     * _at_param mgrsp to convert.
     * _at_param ellip Ellipsoid for earth model.
     * _at_param llp a LatLonPoint to fill in values for. If null, a new
     * LatLonPoint will be returned. If not null, the new
     * values will be set in this object, and it will be
     * returned.
     * _at_return LatLonPoint with values converted from MGRS coordinate.
     */
    public static LatLonPoint MGRStoLL(MGRSPoint mgrsp, Ellipsoid ellip,
                                       LatLonPoint llp) {
        return UTMtoLL(mgrsp, ellip, llp);
    }

    /**
     * Converts a LatLonPoint to a MGRS Point, assuming the WGS_84
     * ellipsoid.
     *
     * _at_return MGRSPoint, or null if something bad happened.
     */
    public static MGRSPoint LLtoMGRS(LatLonPoint llpoint) {
        return LLtoMGRS(llpoint, Ellipsoid.WGS_84, new MGRSPoint());
    }

    /**
     * Converts a LatLonPoint to a MGRS Point.
     *
     * _at_param llpoint the LatLonPoint to convert.
     * _at_param mgrsp a MGRSPoint to put the results in. If it's null, a
     * MGRSPoint will be allocated.
     * _at_return MGRSPoint, or null if something bad happened. If a
     * MGRSPoint was passed in, it will also be returned on a
     * successful conversion.
     */
    public static MGRSPoint LLtoMGRS(LatLonPoint llpoint, MGRSPoint mgrsp) {
        return LLtoMGRS(llpoint, Ellipsoid.WGS_84, mgrsp);
    }

    /**
     * Create a MGRSPoint from a LatLonPoint.
     *
     * _at_param llp LatLonPoint to convert.
     * _at_param ellip Ellipsoid for earth model.
     * _at_param mgrsp a MGRSPoint to fill in values for. If null, a new
     * MGRSPoint will be returned. If not null, the new values
     * will be set in this object, and it will be returned.
     * _at_return MGRSPoint with values converted from lat/lon.
     */
    public static MGRSPoint LLtoMGRS(LatLonPoint llp, Ellipsoid ellip,
                                     MGRSPoint mgrsp) {
        mgrsp = (MGRSPoint) LLtoUTM(llp, ellip, mgrsp);
        mgrsp.resolve();
        return mgrsp;
    }

    /**
     * Method that provides a check for MGRS zone letters. Returns an
     * uppercase version of any valid letter passed in.
     */
    protected char checkZone(char zone) {
        zone = Character.toUpperCase(zone);

        if (zone <= 'A' || zone == 'B' || zone == 'Y' || zone >= 'Z'
                || zone == 'I' || zone == 'O') {
            throw new NumberFormatException("Invalid MGRSPoint zone letter: "
                    + zone);
        }

        return zone;
    }

    /**
     * Determines the correct MGRS letter designator for the given
     * latitude returns 'Z' if latitude is outside the MGRS limits of
     * 84N to 80S.
     *
     * _at_param Lat The float value of the latitude.
     *
     * _at_return A char value which is the MGRS zone letter.
     */
    protected char getLetterDesignator(double lat) {

        //This is here as an error flag to show that the Latitude is
        //outside MGRS limits
        char LetterDesignator = 'Z';

        if ((84 >= lat) && (lat >= 72))
            LetterDesignator = 'X';
        else if ((72 > lat) && (lat >= 64))
            LetterDesignator = 'W';
        else if ((64 > lat) && (lat >= 56))
            LetterDesignator = 'V';
        else if ((56 > lat) && (lat >= 48))
            LetterDesignator = 'U';
        else if ((48 > lat) && (lat >= 40))
            LetterDesignator = 'T';
        else if ((40 > lat) && (lat >= 32))
            LetterDesignator = 'S';
        else if ((32 > lat) && (lat >= 24))
            LetterDesignator = 'R';
        else if ((24 > lat) && (lat >= 16))
            LetterDesignator = 'Q';
        else if ((16 > lat) && (lat >= 8))
            LetterDesignator = 'P';
        else if ((8 > lat) && (lat >= 0))
            LetterDesignator = 'N';
        else if ((0 > lat) && (lat >= -8))
            LetterDesignator = 'M';
        else if ((-8 > lat) && (lat >= -16))
            LetterDesignator = 'L';
        else if ((-16 > lat) && (lat >= -24))
            LetterDesignator = 'K';
        else if ((-24 > lat) && (lat >= -32))
            LetterDesignator = 'J';
        else if ((-32 > lat) && (lat >= -40))
            LetterDesignator = 'H';
        else if ((-40 > lat) && (lat >= -48))
            LetterDesignator = 'G';
        else if ((-48 > lat) && (lat >= -56))
            LetterDesignator = 'F';
        else if ((-56 > lat) && (lat >= -64))
            LetterDesignator = 'E';
        else if ((-64 > lat) && (lat >= -72))
            LetterDesignator = 'D';
        else if ((-72 > lat) && (lat >= -80))
            LetterDesignator = 'C';
        return LetterDesignator;
    }

    /**
     * Set the number of digits to use for easting and northing
     * numbers in the mgrs string, which reflects the accuracy of the
     * corrdinate. From 5 (1 meter) to 1 (10,000 meter).
     */
    public void setAccuracy(int value) {
        accuracy = value;
        mgrs = null;
    }

    public int getAccuracy() {
        return accuracy;
    }

    /**
     * Set the UTM parameters from a MGRS string.
     *
     * _at_param mgrsString an UPPERCASE coordinate string is expected.
     */
    protected void decode(String mgrsString) throws NumberFormatException {

        if (mgrsString == null || mgrsString.length() == 0) {
            throw new NumberFormatException("MGRSPoint coverting from nothing");
        }

        int length = mgrsString.length();

        String hunK = null;
        String seasting = null;
        String snorthing = null;

        StringBuffer sb = new StringBuffer();
        char testChar;
        int i = 0;

        // get Zone number
        while (!Character.isLetter(testChar = mgrsString.charAt(i))) {
            if (i > 2) {
                throw new NumberFormatException("MGRSPoint bad conversion from: "
                        + mgrsString);
            }
            sb.append(testChar);
            i++;
        }

        zone_number = Integer.parseInt(sb.toString());

        if (i == 0 || i + 3 > length) {
            // A good MGRS string has to be 4-5 digits long,
            // ##AAA/#AAA at least.
            throw new NumberFormatException("MGRSPoint bad conversion from: "
                    + mgrsString);
        }

        zone_letter = mgrsString.charAt(i++);

        // Should we check the zone letter here? Why not.
        if (zone_letter <= 'A' || zone_letter == 'B' || zone_letter == 'Y'
                || zone_letter >= 'Z' || zone_letter == 'I'
                || zone_letter == 'O') {
            throw new NumberFormatException("MGRSPoint zone letter "
                    + (char) zone_letter + " not handled: " + mgrsString);
        }

        hunK = mgrsString.substring(i, i += 2);

        int set = get100kSetForZone(zone_number);

        float east100k = getEastingFromChar(hunK.charAt(0), set);
        float north100k = getNorthingFromChar(hunK.charAt(1), set);

        // We have a bug where the northing may be 2000000 too low.
        // How
        // do we know when to roll over?

        while (north100k < getMinNorthing(zone_letter)) {
            north100k += 2000000;
        }

        // calculate the char index for easting/northing separator
        int remainder = length - i;

        if (remainder % 2 != 0) {
            throw new NumberFormatException("MGRSPoint has to have an even number \nof digits after the zone letter and two 100km letters - front \nhalf for easting meters, second half for \nnorthing meters"
                    + mgrsString);
        }

        int sep = remainder / 2;

        float sepEasting = 0f;
        float sepNorthing = 0f;

        if (sep > 0) {
            if (DEBUG)
                Debug.output(" calculating e/n from " + mgrs.substring(i));
            float accuracyBonus = 100000f / (float) Math.pow(10, sep);
            if (DEBUG)
                Debug.output(" calculated accuracy bonus as " + accuracyBonus);
            String sepEastingString = mgrsString.substring(i, i + sep);
            if (DEBUG)
                Debug.output(" parsed easting as " + sepEastingString);
            sepEasting = Float.parseFloat(sepEastingString) * accuracyBonus;
            String sepNorthingString = mgrsString.substring(i + sep);
            if (DEBUG)
                Debug.output(" parsed northing as " + sepNorthingString);
            sepNorthing = Float.parseFloat(sepNorthingString) * accuracyBonus;
        }

        easting = sepEasting + east100k;
        northing = sepNorthing + north100k;

        if (DEBUG) {
            Debug.output("Decoded " + mgrsString + " as zone number: "
                    + zone_number + ", zone letter: " + zone_letter
                    + ", easting: " + easting + ", northing: " + northing
                    + ", 100k: " + hunK);
        }
    }

    /**
     * Create the mgrs string based on the internal UTM settings,
     * using the accuracy set in the MGRSPoint.
     */
    protected void resolve() {
        resolve(accuracy);
    }

    /**
     * Create the mgrs string based on the internal UTM settings.
     *
     * _at_param digitAccuracy The number of digits to use for the
     * northing and easting numbers. 5 digits reflect a 1 meter
     * accuracy, 4 - 10 meter, 3 - 100 meter, 2 - 1000 meter, 1 -
     * 10,000 meter.
     */
    protected void resolve(int digitAccuracy) {
        if (zone_letter == 'Z') {
            mgrs = "Latitude limit exceeded";
        } else {
            StringBuffer sb = new StringBuffer(zone_number + ""
                    + (char) zone_letter
                    + get100kID(easting, northing, zone_number));
            StringBuffer seasting = new StringBuffer(Integer.toString((int) easting));
            StringBuffer snorthing = new StringBuffer(Integer.toString((int) northing));

            if (DEBUG) {
                Debug.output(" Resolving MGRS from easting: " + seasting
                        + " derived from " + easting + ", and northing: "
                        + snorthing + " derived from " + northing);
            }

            while (digitAccuracy + 1 > seasting.length()) {
                seasting.insert(0, '0');
            }

            // We have to be careful here, the 100k values shouldn't
            // be
            // used for calculating stuff here.

            while (digitAccuracy + 1 > snorthing.length()) {
                snorthing.insert(0, '0');
            }

            while (snorthing.length() > 6) {
                snorthing.deleteCharAt(0);
            }

            if (DEBUG) {
                Debug.output(" -- modified easting: " + seasting
                        + " and northing: " + snorthing);
            }

            try {
                sb.append(seasting.substring(1, digitAccuracy + 1)
                        + snorthing.substring(1, digitAccuracy + 1));

                mgrs = sb.toString();
            } catch (IndexOutOfBoundsException ioobe) {
                mgrs = null;
            }
        }
    }

    /**
     * Given a UTM zone number, figure out the MGRS 100K set it is in.
     */
    protected int get100kSetForZone(int i) {
        int set = i % NUM_100K_SETS;
        if (set == 0)
            set = NUM_100K_SETS;
        return set;
    }

    /**
     * Provided so that extensions to this class can provide different
     * origin letters, in case of different ellipsoids. The int[]
     * represents all of the first letters in the bottom left corner
     * of each set box, as shown in an MGRS 100K box layout.
     */
    protected int[] getOriginColumnLetters() {
        return originColumnLetters;
    }

    /**
     * Provided so that extensions to this class can provide different
     * origin letters, in case of different ellipsoids. The int[]
     * represents all of the first letters in the bottom left corner
     * of each set box, as shown in an MGRS 100K box layout.
     */
    protected void setOriginColumnLetters(int[] letters) {
        originColumnLetters = letters;
    }

    /**
     * Provided so that extensions to this class can provide different
     * origin letters, in case of different ellipsoids. The int[]
     * represents all of the second letters in the bottom left corner
     * of each set box, as shown in an MGRS 100K box layout.
     */
    protected int[] getOriginRowLetters() {
        return originRowLetters;
    }

    /**
     * Provided so that extensions to this class can provide different
     * origin letters, in case of different ellipsoids. The int[]
     * represents all of the second letters in the bottom left corner
     * of each set box, as shown in an MGRS 100K box layout.
     */
    protected void setOriginRowLetters(int[] letters) {
        originRowLetters = letters;
    }

    /**
     * Get the two letter 100k designator for a given UTM easting,
     * northing and zone number value.
     */
    protected String get100kID(float easting, float northing, int zone_number) {
        int set = get100kSetForZone(zone_number);
        int setColumn = ((int) easting / 100000);
        int setRow = ((int) northing / 100000) % 20;
        return get100kID(setColumn, setRow, set);
    }

    /**
     * Given the first letter from a two-letter MGRS 100k zone, and
     * given the MGRS table set for the zone number, figure out the
     * easting value that should be added to the other, secondary
     * easting value.
     */
    protected float getEastingFromChar(char e, int set) {
        int baseCol[] = getOriginColumnLetters();
        // colOrigin is the letter at the origin of the set for the
        // column
        int curCol = baseCol[set - 1];
        float eastingValue = 100000f;
        boolean rewindMarker = false;
        
        while (curCol != e) {
            curCol++;
            if (curCol == I)
                curCol++;
            if (curCol == O)
                curCol++;
            if (curCol > Z) {
                if(rewindMarker) {
                    throw new NumberFormatException("Bad character: "+e);
                }
                curCol = A;
                rewindMarker = true;
            }
            eastingValue += 100000f;
        }

        if (DEBUG) {
            Debug.output("Easting value for " + (char) e + " from set: " + set
                    + ", col: " + curCol + " is " + eastingValue);
        }
        return eastingValue;
    }

    /**
     * Given the second letter from a two-letter MGRS 100k zone, and
     * given the MGRS table set for the zone number, figure out the
     * northing value that should be added to the other, secondary
     * northing value. You have to remember that Northings are
     * determined from the equator, and the vertical cycle of letters
     * mean a 2000000 additional northing meters. This happens approx.
     * every 18 degrees of latitude. This method does *NOT* count any
     * additional northings. You have to figure out how many 2000000
     * meters need to be added for the zone letter of the MGRS
     * coordinate.
     *
     * _at_param n second letter of the MGRS 100k zone
     * _at_param set the MGRS table set number, which is dependent on the
     * UTM zone number.
     */
    protected float getNorthingFromChar(char n, int set) {

        if (n > 'V')
            throw new NumberFormatException("MGRSPoint given invalid Northing "
                    + n);

        int baseRow[] = getOriginRowLetters();
        // rowOrigin is the letter at the origin of the set for the
        // column
        int curRow = baseRow[set - 1];
        float northingValue = 0f;
        boolean rewindMarker = false;

        while (curRow != n) {
            curRow++;
            if (curRow == I)
                curRow++;
            if (curRow == O)
                curRow++;
            // fixing a bug making whole application hang in this loop
            // when 'n' is a wrong character
            if (curRow > V) {
                if(rewindMarker) { //making sure that this loop ends
                    throw new NumberFormatException("Bad character: "+n);
                }
                curRow = A;
                rewindMarker = true;
            }
            northingValue += 100000f;
        }

        if (DEBUG) {
            Debug.output("Northing value for " + (char) n + " from set: " + set
                    + ", row: " + curRow + " is " + northingValue);
        }

        return northingValue;
    }

    /**
     * Get the two-letter MGRS 100k designator given information
     * translated from the UTM northing, easting and zone number.
     *
     * _at_param setColumn the column index as it relates to the MGRS
     * 100k set spreadsheet, created from the UTM easting.
     * Values are 1-8.
     * _at_param setRow the row index as it relates to the MGRS 100k set
     * spreadsheet, created from the UTM northing value. Values
     * are from 0-19.
     * _at_param set the set block, as it relates to the MGRS 100k set
     * spreadsheet, created from the UTM zone. Values are from
     * 1-60.
     * _at_return two letter MGRS 100k code.
     */
    protected String get100kID(int setColumn, int setRow, int set) {

        if (DEBUG) {
            System.out.println("set (" + set + ") column = " + setColumn
                    + ", row = " + setRow);
        }

        int baseCol[] = getOriginColumnLetters();
        int baseRow[] = getOriginRowLetters();

        // colOrigin and rowOrigin are the letters at the origin of
        // the set
        int colOrigin = baseCol[set - 1];
        int rowOrigin = baseRow[set - 1];

        if (DEBUG) {
            System.out.println("starting at = " + (char) colOrigin
                    + (char) rowOrigin);
        }

        // colInt and rowInt are the letters to build to return
        int colInt = colOrigin + setColumn - 1;
        int rowInt = rowOrigin + setRow;
        boolean rollover = false;

        if (colInt > Z) {
            colInt = colInt - Z + A - 1;
            rollover = true;
            if (DEBUG)
                System.out.println("rolling over col, new value: "
                        + (char) colInt);
        }

        if (colInt == I || (colOrigin < I && colInt > I)
                || ((colInt > I || colOrigin < I) && rollover)) {
            colInt++;
            if (DEBUG)
                System.out.println("skipping I in col, new value: "
                        + (char) colInt);
        }
        if (colInt == O || (colOrigin < O && colInt > O)
                || ((colInt > O || colOrigin < O) && rollover)) {
            colInt++;
            if (DEBUG)
                System.out.println("skipping O in col, new value: "
                        + (char) colInt);
            if (colInt == I) {
                colInt++;
                if (DEBUG)
                    System.out.println(" hit I, new value: " + (char) colInt);
            }
        }

        if (colInt > Z) {
            colInt = colInt - Z + A - 1;
            if (DEBUG)
                System.out.println("rolling(2) col, new value: "
                        + (char) rowInt);
        }

        if (rowInt > V) {
            rowInt = rowInt - V + A - 1;
            rollover = true;
            if (DEBUG)
                System.out.println("rolling over row, new value: "
                        + (char) rowInt);
        } else {
            rollover = false;
        }

        if (rowInt == I || (rowOrigin < I && rowInt > I)
                || ((rowInt > I || rowOrigin < I) && rollover)) {
            rowInt++;
            if (DEBUG)
                System.out.println("skipping I in row, new value: "
                        + (char) rowInt);
        }

        if (rowInt == O || (rowOrigin < O && rowInt > O)
                || ((rowInt > O || rowOrigin < O) && rollover)) {
            rowInt++;
            if (DEBUG)
                System.out.println("skipping O in row, new value: "
                        + (char) rowInt);
            if (rowInt == I) {
                rowInt++;
                if (DEBUG)
                    System.out.println(" hit I, new value: " + (char) rowInt);
            }
        }

        if (rowInt > V) {
            rowInt = rowInt - V + A - 1;
            if (DEBUG)
                System.out.println("rolling(2) row, new value: "
                        + (char) rowInt);
        }

        String twoLetter = (char) colInt + "" + (char) rowInt;

        if (DEBUG) {
            System.out.println("ending at = " + twoLetter);
        }

        return twoLetter;
    }

    /**
     * Testing method, used to print out the MGRS 100k two letter set
     * tables.
     */
    protected void print100kSets() {
        StringBuffer sb = null;
        for (int set = 1; set <= 6; set++) {
            System.out.println("-------------\nFor 100K Set " + set
                    + ":\n-------------\n");
            for (int i = 19; i >= 0; i -= 1) {
                sb = new StringBuffer((i * 100000) + "\t| ");

                for (int j = 1; j <= 8; j++) {
                    sb.append(" " + get100kID(j, i, set));
                }

                sb.append(" |");
                System.out.println(sb);
            }
        }
    }

    /**
     * The function getMinNorthing returns the minimum northing value
     * of a MGRS zone.
     *
     * portted from Geotrans' c Lattitude_Band_Value strucure table.
     * zoneLetter : MGRS zone (input)
     */

    protected float getMinNorthing(char zoneLetter)
            throws NumberFormatException {
        float northing;
        switch (zoneLetter) {
        case 'C':
            northing = 1100000.0f;
            break;
        case 'D':
            northing = 2000000.0f;
            break;
        case 'E':
            northing = 2800000.0f;
            break;
        case 'F':
            northing = 3700000.0f;
            break;
        case 'G':
            northing = 4600000.0f;
            break;
        case 'H':
            northing = 5500000.0f;
            break;
        case 'J':
            northing = 6400000.0f;
            break;
        case 'K':
            northing = 7300000.0f;
            break;
        case 'L':
            northing = 8200000.0f;
            break;
        case 'M':
            northing = 9100000.0f;
            break;
        case 'N':
            northing = 0.0f;
            break;
        case 'P':
            northing = 800000.0f;
            break;
        case 'Q':
            northing = 1700000.0f;
            break;
        case 'R':
            northing = 2600000.0f;
            break;
        case 'S':
            northing = 3500000.0f;
            break;
        case 'T':
            northing = 4400000.0f;
            break;
        case 'U':
            northing = 5300000.0f;
            break;
        case 'V':
            northing = 6200000.0f;
            break;
        case 'W':
            northing = 7000000.0f;
            break;
        case 'X':
            northing = 7900000.0f;
            break;
        default:
            northing = -1.0f;
        }
        if (northing >= 0.0) {
            return northing;
        } else {
            throw new NumberFormatException("Invalid zone letter: "
                    + zone_letter);
        }

    }

    private static void runTests(String fName, String inType) {

        LineNumberReader lnr = null;
        PrintStream pos = null;
        String record = null;
        StringBuffer outStr1 = new StringBuffer();
        StringBuffer outStr2 = new StringBuffer();

        try {

            /*
             * File inFile = new File(fName + ".dat"); File outFile =
             * new File(fName + ".out"); FileInputStream fis = new
             * FileInputStream(inFile); FileOutputStream fos = new
             * FileOutputStream(outFile); BufferedInputStream bis =
             * new BufferedInputStream(fis);
             */
            pos = new PrintStream(new FileOutputStream(new File(fName + ".out")));
            lnr = new LineNumberReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(new File(fName)))));

            if (inType.equalsIgnoreCase("MGRS")) {
                outStr1.append("MGRS to LatLonPoint\n\tMGRS\t\tLatitude Longitude\n");
                outStr2.append("MGRS to UTM\n\tMGRS\t\tZone Easting Northing\n");
            } else if (inType.equalsIgnoreCase("UTM")) {
                outStr1.append("UTM to LatLonPoint\n\tUTM\t\tLatitude Longitude\n");
                outStr2.append("UTM to MGRS\n\tUTM\t\tMGRS\n");
            } else if (inType.equalsIgnoreCase("LatLon")) {
                outStr1.append("LatLonPoint to UTM\nLatitude Longitude\t\tZone Easting Northing \n");
                outStr2.append("LatLonPoint to MGRS\nLatitude Longitude\t\tMGRS\n");
            }

            while ((record = lnr.readLine()) != null) {
                if (inType.equalsIgnoreCase("MGRS")) {
                    try {
                        MGRSPoint mgrsp = new MGRSPoint(record);
                        record.trim();
                        mgrsp.decode(record);

                        outStr1.append(record + " is " + mgrsp.toLatLonPoint()
                                + "\n");
                        outStr2.append(record + " to UTM: " + mgrsp.zone_number
                                + " " + mgrsp.easting + " " + mgrsp.northing
                                + "\n");
                    } catch (NumberFormatException nfe) {
                        Debug.error(nfe.getMessage());
                    }

                } else if (inType.equalsIgnoreCase("UTM")) {
                    MGRSPoint mgrsp;
                    UTMPoint utmp;
                    float e, n;
                    int z;
                    char zl;
                    String tmp;
                    record.trim();
                    tmp = record.substring(0, 2);
                    z = Integer.parseInt(tmp);
                    tmp = record.substring(5, 11);
                    e = Float.parseFloat(tmp);
                    tmp = record.substring(12, 19);
                    n = Float.parseFloat(tmp);
                    zl = record.charAt(3);
                    utmp = new UTMPoint(n, e, z, zl);
                    LatLonPoint llp = utmp.toLatLonPoint();
                    mgrsp = LLtoMGRS(llp);
                    outStr1.append(record + " is " + llp + " back to "
                            + LLtoUTM(llp) + "\n");
                    outStr2.append(record + " is " + mgrsp + "\n");
                } else if (inType.equalsIgnoreCase("LatLon")) {
                    MGRSPoint mgrsp;
                    UTMPoint utmp;
                    LatLonPoint llp;
                    float lat, lon;
                    int index;
                    String tmp;
                    record.trim();
                    index = record.indexOf("\040");
                    if (index < 0) {
                        index = record.indexOf("\011");
                    }
                    tmp = record.substring(0, index);
                    lat = Float.parseFloat(tmp);
                    tmp = record.substring(index);
                    lon = Float.parseFloat(tmp);
                    llp = new LatLonPoint(lat, lon);
                    utmp = LLtoUTM(llp);
                    mgrsp = LLtoMGRS(llp);
                    outStr1.append(record + " to UTM: " + mgrsp.zone_number
                            + " " + mgrsp.easting + " " + mgrsp.northing + "\n");
                    outStr2.append(record + " -> " + mgrsp.mgrs + "\n");
                }

            }

        } catch (IOException e) {
            // catch io errors from FileInputStream or readLine()
            System.out.println("IO error: " + e.getMessage());

        } finally {
            if (pos != null) {
                pos.print(outStr1.toString());
                pos.print("\n");
                pos.print(outStr2.toString());
                pos.close();
            }
            // if the file opened okay, make sure we close it
            if (lnr != null) {
                try {
                    lnr.close();
                } catch (IOException ioe) {
                }
            }

        }

    }

    public static void main(String[] argv) {
        Debug.init();

        ArgParser ap = new ArgParser("MGRSPoint");
        ap.add("mgrs", "Print Latitude and Longitude for MGRS value", 1);
        ap.add("latlon",
                "Print MGRS for Latitude and Longitude values",
                2,
                true);
        ap.add("sets", "Print the MGRS 100k table");
        ap.add("altsets", "Print the MGRS 100k table for the Bessel ellipsoid");
        ap.add("rtc",
                "Run test case, with filename and input data type [MGRS | UTM | LatLon]",
                2);

        if (!ap.parse(argv)) {
            ap.printUsage();
            System.exit(0);
        }

        String arg[];
        arg = ap.getArgValues("sets");
        if (arg != null) {
            new MGRSPoint().print100kSets();
        }

        arg = ap.getArgValues("altsets");
        if (arg != null) {
            MGRSPoint mgrsp = new MGRSPoint();
            mgrsp.setOriginColumnLetters(BESSEL_SET_ORIGIN_COLUMN_LETTERS);
            mgrsp.setOriginRowLetters(BESSEL_SET_ORIGIN_ROW_LETTERS);
            mgrsp.print100kSets();
        }

        arg = ap.getArgValues("mgrs");
        if (arg != null) {
            try {
                MGRSPoint mgrsp = new MGRSPoint(arg[0]);
                Debug.output(arg[0] + " is " + mgrsp.toLatLonPoint());
            } catch (NumberFormatException nfe) {
                Debug.error(nfe.getMessage());
            }
        }

        arg = ap.getArgValues("latlon");
        if (arg != null) {
            try {

                float lat = Float.parseFloat(arg[0]);
                float lon = Float.parseFloat(arg[1]);

                LatLonPoint llp = new LatLonPoint(lat, lon);
                MGRSPoint mgrsp = LLtoMGRS(llp);
                UTMPoint utmp = LLtoUTM(llp);

                if (utmp.zone_letter == 'Z') {
                    Debug.output(llp + "to UTM: latitude limit exceeded.");
                } else {
                    Debug.output(llp + " is " + utmp);
                }

                Debug.output(llp + " is " + mgrsp);

            } catch (NumberFormatException nfe) {
                Debug.error("The numbers provided: " + argv[0] + ", "
                        + argv[1] + " aren't valid");
            }
        }

        arg = ap.getArgValues("rtc");
        if (arg != null) {
            runTests(arg[0], arg[1]);
        }

    }
}



/*
 * _at_(#)Quantize.java 0.90 9/19/00 Adam Doppelt
 */

/**
 * An efficient color quantization algorithm, adapted from the C++
 * implementation quantize.c in <a
 * href="http://www.imagemagick.org/">ImageMagick</a>. The pixels for
 * an image are placed into an oct tree. The oct tree is reduced in
 * size, and the pixels from the original image are reassigned to the
 * nodes in the reduced tree.<p>
 *
 * Here is the copyright notice from ImageMagick:
 *
 * <pre>
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Permission is hereby granted, free of charge, to any person obtaining a %
% copy of this software and associated documentation files ("ImageMagick"), %
% to deal in ImageMagick without restriction, including without limitation %
% the rights to use, copy, modify, merge, publish, distribute, sublicense, %
% and/or sell copies of ImageMagick, and to permit persons to whom the %
% ImageMagick is furnished to do so, subject to the following conditions: %
% %
% The above copyright notice and this permission notice shall be included in %
% all copies or substantial portions of ImageMagick. %
% %
% The software is provided "as is", without warranty of any kind, express or %
% implied, including but not limited to the warranties of merchantability, %
% fitness for a particular purpose and noninfringement. In no event shall %
% E. I. du Pont de Nemours and Company be liable for any claim, damages or %
% other liability, whether in an action of contract, tort or otherwise, %
% arising from, out of or in connection with ImageMagick or the use or other %
% dealings in ImageMagick. %
% %
% Except as contained in this notice, the name of the E. I. du Pont de %
% Nemours and Company shall not be used in advertising or otherwise to %
% promote the sale, use or other dealings in ImageMagick without prior %
% written authorization from the E. I. du Pont de Nemours and Company. %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
</pre>
 *
 *
 * _at_version 0.90 19 Sep 2000
 * _at_author <a href="http://www.gurge.com/amd/">Adam Doppelt</a>
 */
package doppelt;

public class Quantize {

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% QQQ U U AAA N N TTTTT IIIII ZZZZZ EEEEE %
% Q Q U U A A NN N T I ZZ E %
% Q Q U U AAAAA N N N T I ZZZ EEEEE %
% Q QQ U U A A N NN T I ZZ E %
% QQQQ UUU A A N N T IIIII ZZZZZ EEEEE %
% %
% %
% Reduce the Number of Unique Colors in an Image %
% %
% %
% Software Design %
% John Cristy %
% July 1992 %
% %
% %
% Copyright 1998 E. I. du Pont de Nemours and Company %
% %
% Permission is hereby granted, free of charge, to any person obtaining a %
% copy of this software and associated documentation files ("ImageMagick"), %
% to deal in ImageMagick without restriction, including without limitation %
% the rights to use, copy, modify, merge, publish, distribute, sublicense, %
% and/or sell copies of ImageMagick, and to permit persons to whom the %
% ImageMagick is furnished to do so, subject to the following conditions: %
% %
% The above copyright notice and this permission notice shall be included in %
% all copies or substantial portions of ImageMagick. %
% %
% The software is provided "as is", without warranty of any kind, express or %
% implied, including but not limited to the warranties of merchantability, %
% fitness for a particular purpose and noninfringement. In no event shall %
% E. I. du Pont de Nemours and Company be liable for any claim, damages or %
% other liability, whether in an action of contract, tort or otherwise, %
% arising from, out of or in connection with ImageMagick or the use or other %
% dealings in ImageMagick. %
% %
% Except as contained in this notice, the name of the E. I. du Pont de %
% Nemours and Company shall not be used in advertising or otherwise to %
% promote the sale, use or other dealings in ImageMagick without prior %
% written authorization from the E. I. du Pont de Nemours and Company. %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Realism in computer graphics typically requires using 24 bits/pixel to
% generate an image. Yet many graphic display devices do not contain
% the amount of memory necessary to match the spatial and color
% resolution of the human eye. The QUANTIZE program takes a 24 bit
% image and reduces the number of colors so it can be displayed on
% raster device with less bits per pixel. In most instances, the
% quantized image closely resembles the original reference image.
%
% A reduction of colors in an image is also desirable for image
% transmission and real-time animation.
%
% Function Quantize takes a standard RGB or monochrome images and quantizes
% them down to some fixed number of colors.
%
% For purposes of color allocation, an image is a set of n pixels, where
% each pixel is a point in RGB space. RGB space is a 3-dimensional
% vector space, and each pixel, pi, is defined by an ordered triple of
% red, green, and blue coordinates, (ri, gi, bi).
%
% Each primary color component (red, green, or blue) represents an
% intensity which varies linearly from 0 to a maximum value, cmax, which
% corresponds to full saturation of that color. Color allocation is
% defined over a domain consisting of the cube in RGB space with
% opposite vertices at (0,0,0) and (cmax,cmax,cmax). QUANTIZE requires
% cmax = 255.
%
% The algorithm maps this domain onto a tree in which each node
% represents a cube within that domain. In the following discussion
% these cubes are defined by the coordinate of two opposite vertices:
% The vertex nearest the origin in RGB space and the vertex farthest
% from the origin.
%
% The tree's root node represents the the entire domain, (0,0,0) through
% (cmax,cmax,cmax). Each lower level in the tree is generated by
% subdividing one node's cube into eight smaller cubes of equal size.
% This corresponds to bisecting the parent cube with planes passing
% through the midpoints of each edge.
%
% The basic algorithm operates in three phases: Classification,
% Reduction, and Assignment. Classification builds a color
% description tree for the image. Reduction collapses the tree until
% the number it represents, at most, the number of colors desired in the
% output image. Assignment defines the output image's color map and
% sets each pixel's color by reclassification in the reduced tree.
% Our goal is to minimize the numerical discrepancies between the original
% colors and quantized colors (quantization error).
%
% Classification begins by initializing a color description tree of
% sufficient depth to represent each possible input color in a leaf.
% However, it is impractical to generate a fully-formed color
% description tree in the classification phase for realistic values of
% cmax. If colors components in the input image are quantized to k-bit
% precision, so that cmax= 2k-1, the tree would need k levels below the
% root node to allow representing each possible input color in a leaf.
% This becomes prohibitive because the tree's total number of nodes is
% 1 + sum(i=1,k,8k).
%
% A complete tree would require 19,173,961 nodes for k = 8, cmax = 255.
% Therefore, to avoid building a fully populated tree, QUANTIZE: (1)
% Initializes data structures for nodes only as they are needed; (2)
% Chooses a maximum depth for the tree as a function of the desired
% number of colors in the output image (currently log2(colormap size)).
%
% For each pixel in the input image, classification scans downward from
% the root of the color description tree. At each level of the tree it
% identifies the single node which represents a cube in RGB space
% containing the pixel's color. It updates the following data for each
% such node:
%
% n1: Number of pixels whose color is contained in the RGB cube
% which this node represents;
%
% n2: Number of pixels whose color is not represented in a node at
% lower depth in the tree; initially, n2 = 0 for all nodes except
% leaves of the tree.
%
% Sr, Sg, Sb: Sums of the red, green, and blue component values for
% all pixels not classified at a lower depth. The combination of
% these sums and n2 will ultimately characterize the mean color of a
% set of pixels represented by this node.
%
% E: The distance squared in RGB space between each pixel contained
% within a node and the nodes' center. This represents the quantization
% error for a node.
%
% Reduction repeatedly prunes the tree until the number of nodes with
% n2 > 0 is less than or equal to the maximum number of colors allowed
% in the output image. On any given iteration over the tree, it selects
% those nodes whose E count is minimal for pruning and merges their
% color statistics upward. It uses a pruning threshold, Ep, to govern
% node selection as follows:
%
% Ep = 0
% while number of nodes with (n2 > 0) > required maximum number of colors
% prune all nodes such that E <= Ep
% Set Ep to minimum E in remaining nodes
%
% This has the effect of minimizing any quantization error when merging
% two nodes together.
%
% When a node to be pruned has offspring, the pruning procedure invokes
% itself recursively in order to prune the tree from the leaves upward.
% n2, Sr, Sg, and Sb in a node being pruned are always added to the
% corresponding data in that node's parent. This retains the pruned
% node's color characteristics for later averaging.
%
% For each node, n2 pixels exist for which that node represents the
% smallest volume in RGB space containing those pixel's colors. When n2
% > 0 the node will uniquely define a color in the output image. At the
% beginning of reduction, n2 = 0 for all nodes except a the leaves of
% the tree which represent colors present in the input image.
%
% The other pixel count, n1, indicates the total number of colors
% within the cubic volume which the node represents. This includes n1 -
% n2 pixels whose colors should be defined by nodes at a lower level in
% the tree.
%
% Assignment generates the output image from the pruned tree. The
% output image consists of two parts: (1) A color map, which is an
% array of color descriptions (RGB triples) for each color present in
% the output image; (2) A pixel array, which represents each pixel as
% an index into the color map array.
%
% First, the assignment phase makes one pass over the pruned color
% description tree to establish the image's color map. For each node
% with n2 > 0, it divides Sr, Sg, and Sb by n2 . This produces the
% mean color of all pixels that classify no lower than this node. Each
% of these colors becomes an entry in the color map.
%
% Finally, the assignment phase reclassifies each pixel in the pruned
% tree to identify the deepest node containing the pixel's color. The
% pixel's value in the pixel array becomes the index of this node's mean
% color in the color map.
%
% With the permission of USC Information Sciences Institute, 4676 Admiralty
% Way, Marina del Rey, California 90292, this code was adapted from module
% ALCOLS written by Paul Raveling.
%
% The names of ISI and USC are not used in advertising or publicity
% pertaining to distribution of the software without prior specific
% written permission from ISI.
%
*/
    
    final static boolean QUICK = true;
    
    final static int MAX_RGB = 255;
    final static int MAX_NODES = 266817;
    final static int MAX_TREE_DEPTH = 8;

    // these are precomputed in advance
    static int SQUARES[];
    static int SHIFT[];

    static {
        SQUARES = new int[MAX_RGB + MAX_RGB + 1];
        for (int i= -MAX_RGB; i <= MAX_RGB; i++) {
            SQUARES[i + MAX_RGB] = i * i;
        }

        SHIFT = new int[MAX_TREE_DEPTH + 1];
        for (int i = 0; i < MAX_TREE_DEPTH + 1; ++i) {
            SHIFT[i] = 1 << (15 - i);
        }
    }

    /**
     * Reduce the image to the given number of colors. The pixels are
     * reduced in place.
     * _at_return The new color palette.
     */
    public static int[] quantizeImage(int pixels[][], int max_colors) {
        Cube cube = new Cube(pixels, max_colors);
        cube.classification();
        cube.reduction();
        cube.assignment();
        return cube.colormap;
    }
    
    static class Cube {
        int pixels[][];
        int max_colors;
        int colormap[];
        
        Node root;
        int depth;

        // counter for the number of colors in the cube. this gets
        // recalculated often.
        int colors;

        // counter for the number of nodes in the tree
        int nodes;

        Cube(int pixels[][], int max_colors) {
            this.pixels = pixels;
            this.max_colors = max_colors;

            int i = max_colors;
            // tree_depth = log max_colors
            // 4
            for (depth = 1; i != 0; depth++) {
                i /= 4;
            }
            if (depth > 1) {
                --depth;
            }
            if (depth > MAX_TREE_DEPTH) {
                depth = MAX_TREE_DEPTH;
            } else if (depth < 2) {
                depth = 2;
            }
            
            root = new Node(this);
        }

        /*
         * Procedure Classification begins by initializing a color
         * description tree of sufficient depth to represent each
         * possible input color in a leaf. However, it is impractical
         * to generate a fully-formed color description tree in the
         * classification phase for realistic values of cmax. If
         * colors components in the input image are quantized to k-bit
         * precision, so that cmax= 2k-1, the tree would need k levels
         * below the root node to allow representing each possible
         * input color in a leaf. This becomes prohibitive because the
         * tree's total number of nodes is 1 + sum(i=1,k,8k).
         *
         * A complete tree would require 19,173,961 nodes for k = 8,
         * cmax = 255. Therefore, to avoid building a fully populated
         * tree, QUANTIZE: (1) Initializes data structures for nodes
         * only as they are needed; (2) Chooses a maximum depth for
         * the tree as a function of the desired number of colors in
         * the output image (currently log2(colormap size)).
         *
         * For each pixel in the input image, classification scans
         * downward from the root of the color description tree. At
         * each level of the tree it identifies the single node which
         * represents a cube in RGB space containing It updates the
         * following data for each such node:
         *
         * number_pixels : Number of pixels whose color is contained
         * in the RGB cube which this node represents;
         *
         * unique : Number of pixels whose color is not represented
         * in a node at lower depth in the tree; initially, n2 = 0
         * for all nodes except leaves of the tree.
         *
         * total_red/green/blue : Sums of the red, green, and blue
         * component values for all pixels not classified at a lower
         * depth. The combination of these sums and n2 will
         * ultimately characterize the mean color of a set of pixels
         * represented by this node.
         */
        void classification() {
            int pixels[][] = this.pixels;

            int width = pixels.length;
            int height = pixels[0].length;

            // convert to indexed color
            for (int x = width; x-- > 0; ) {
                for (int y = height; y-- > 0; ) {
                    int pixel = pixels[x][y];
                    int red = (pixel >> 16) & 0xFF;
                    int green = (pixel >> 8) & 0xFF;
                    int blue = (pixel >> 0) & 0xFF;

                    // a hard limit on the number of nodes in the tree
                    if (nodes > MAX_NODES) {
                        System.out.println("pruning");
                        root.pruneLevel();
                        --depth;
                    }

                    // walk the tree to depth, increasing the
                    // number_pixels count for each node
                    Node node = root;
                    for (int level = 1; level <= depth; ++level) {
                        int id = (((red > node.mid_red ? 1 : 0) << 0) |
                                  ((green > node.mid_green ? 1 : 0) << 1) |
                                  ((blue > node.mid_blue ? 1 : 0) << 2));
                        if (node.child[id] == null) {
                            new Node(node, id, level);
                        }
                        node = node.child[id];
                        node.number_pixels += SHIFT[level];
                    }

                    ++node.unique;
                    node.total_red += red;
                    node.total_green += green;
                    node.total_blue += blue;
                }
            }
        }

        /*
         * reduction repeatedly prunes the tree until the number of
         * nodes with unique > 0 is less than or equal to the maximum
         * number of colors allowed in the output image.
         *
         * When a node to be pruned has offspring, the pruning
         * procedure invokes itself recursively in order to prune the
         * tree from the leaves upward. The statistics of the node
         * being pruned are always added to the corresponding data in
         * that node's parent. This retains the pruned node's color
         * characteristics for later averaging.
         */
        void reduction() {
            long threshold = 1;
            while (colors > max_colors) {
                colors = 0;
                threshold = root.reduce(threshold, Long.MAX_VALUE);
            }
        }

        /**
         * The result of a closest color search.
         */
        static class Search {
            int distance;
            int color_number;
        }

        /*
         * Procedure assignment generates the output image from the
         * pruned tree. The output image consists of two parts: (1) A
         * color map, which is an array of color descriptions (RGB
         * triples) for each color present in the output image; (2) A
         * pixel array, which represents each pixel as an index into
         * the color map array.
         *
         * First, the assignment phase makes one pass over the pruned
         * color description tree to establish the image's color map.
         * For each node with n2 > 0, it divides Sr, Sg, and Sb by n2.
         * This produces the mean color of all pixels that classify no
         * lower than this node. Each of these colors becomes an entry
         * in the color map.
         *
         * Finally, the assignment phase reclassifies each pixel in
         * the pruned tree to identify the deepest node containing the
         * pixel's color. The pixel's value in the pixel array becomes
         * the index of this node's mean color in the color map.
         */
        void assignment() {
            colormap = new int[colors];

            colors = 0;
            root.colormap();
  
            int pixels[][] = this.pixels;

            int width = pixels.length;
            int height = pixels[0].length;

            Search search = new Search();
            
            // convert to indexed color
            for (int x = width; x-- > 0; ) {
                for (int y = height; y-- > 0; ) {
                    int pixel = pixels[x][y];
                    int red = (pixel >> 16) & 0xFF;
                    int green = (pixel >> 8) & 0xFF;
                    int blue = (pixel >> 0) & 0xFF;

                    // walk the tree to find the cube containing that color
                    Node node = root;
                    for ( ; ; ) {
                        int id = (((red > node.mid_red ? 1 : 0) << 0) |
                                  ((green > node.mid_green ? 1 : 0) << 1) |
                                  ((blue > node.mid_blue ? 1 : 0) << 2) );
                        if (node.child[id] == null) {
                            break;
                        }
                        node = node.child[id];
                    }

                    if (QUICK) {
                        // if QUICK is set, just use that
                        // node. Strictly speaking, this isn't
                        // necessarily best match.
                        pixels[x][y] = node.color_number;
                    } else {
                        // Find the closest color.
                        search.distance = Integer.MAX_VALUE;
                        node.parent.closestColor(red, green, blue, search);
                        pixels[x][y] = search.color_number;
                    }
                }
            }
        }

        /**
         * A single Node in the tree.
         */
        static class Node {
            Cube cube;

            // parent node
            Node parent;

            // child nodes
            Node child[];
            int nchild;

            // our index within our parent
            int id;
            // our level within the tree
            int level;
            // our color midpoint
            int mid_red;
            int mid_green;
            int mid_blue;

            // the pixel count for this node and all children
            long number_pixels;
            
            // the pixel count for this node
            int unique;
            // the sum of all pixels contained in this node
            int total_red;
            int total_green;
            int total_blue;

            // used to build the colormap
            int color_number;

            Node(Cube cube) {
                this.cube = cube;
                this.parent = this;
                this.child = new Node[8];
                this.id = 0;
                this.level = 0;

                this.number_pixels = Long.MAX_VALUE;
            
                this.mid_red = (MAX_RGB + 1) >> 1;
                this.mid_green = (MAX_RGB + 1) >> 1;
                this.mid_blue = (MAX_RGB + 1) >> 1;
            }
        
            Node(Node parent, int id, int level) {
                this.cube = parent.cube;
                this.parent = parent;
                this.child = new Node[8];
                this.id = id;
                this.level = level;

                // add to the cube
                ++cube.nodes;
                if (level == cube.depth) {
                    ++cube.colors;
                }

                // add to the parent
                ++parent.nchild;
                parent.child[id] = this;

                // figure out our midpoint
                int bi = (1 << (MAX_TREE_DEPTH - level)) >> 1;
                mid_red = parent.mid_red + ((id & 1) > 0 ? bi : -bi);
                mid_green = parent.mid_green + ((id & 2) > 0 ? bi : -bi);
                mid_blue = parent.mid_blue + ((id & 4) > 0 ? bi : -bi);
            }

            /**
             * Remove this child node, and make sure our parent
             * absorbs our pixel statistics.
             */
            void pruneChild() {
                --parent.nchild;
                parent.unique += unique;
                parent.total_red += total_red;
                parent.total_green += total_green;
                parent.total_blue += total_blue;
                parent.child[id] = null;
                --cube.nodes;
                cube = null;
                parent = null;
            }

            /**
             * Prune the lowest layer of the tree.
             */
            void pruneLevel() {
                if (nchild != 0) {
                    for (int id = 0; id < 8; id++) {
                        if (child[id] != null) {
                            child[id].pruneLevel();
                        }
                    }
                }
                if (level == cube.depth) {
                    pruneChild();
                }
            }

            /**
             * Remove any nodes that have fewer than threshold
             * pixels. Also, as long as we're walking the tree:
             *
             * - figure out the color with the fewest pixels
             * - recalculate the total number of colors in the tree
             */
            long reduce(long threshold, long next_threshold) {
                if (nchild != 0) {
                    for (int id = 0; id < 8; id++) {
                        if (child[id] != null) {
                            next_threshold = child[id].reduce(threshold, next_threshold);
                        }
                    }
                }
                if (number_pixels <= threshold) {
                    pruneChild();
                } else {
                    if (unique != 0) {
                        cube.colors++;
                    }
                    if (number_pixels < next_threshold) {
                        next_threshold = number_pixels;
                    }
                }
                return next_threshold;
            }

            /*
             * colormap traverses the color cube tree and notes each
             * colormap entry. A colormap entry is any node in the
             * color cube tree where the number of unique colors is
             * not zero.
             */
            void colormap() {
                if (nchild != 0) {
                    for (int id = 0; id < 8; id++) {
                        if (child[id] != null) {
                            child[id].colormap();
                        }
                    }
                }
                if (unique != 0) {
                    int r = ((total_red + (unique >> 1)) / unique);
                    int g = ((total_green + (unique >> 1)) / unique);
                    int b = ((total_blue + (unique >> 1)) / unique);
                    cube.colormap[cube.colors] = ((( 0xFF) << 24) |
                                                  ((r & 0xFF) << 16) |
                                                  ((g & 0xFF) << 8) |
                                                  ((b & 0xFF) << 0));
                    color_number = cube.colors++;
                }
            }

            /* ClosestColor traverses the color cube tree at a
             * particular node and determines which colormap entry
             * best represents the input color.
             */
            void closestColor(int red, int green, int blue, Search search) {
                if (nchild != 0) {
                    for (int id = 0; id < 8; id++) {
                        if (child[id] != null) {
                            child[id].closestColor(red, green, blue, search);
                        }
                    }
                }

                if (unique != 0) {
                    int color = cube.colormap[color_number];
                    int distance = distance(color, red, green, blue);
                    if (distance < search.distance) {
                        search.distance = distance;
                        search.color_number = color_number;
                    }
                }
            }

            /**
             * Figure out the distance between this node and som color.
             */
            final static int distance(int color, int r, int g, int b) {
                return (SQUARES[((color >> 16) & 0xFF) - r + MAX_RGB] +
                        SQUARES[((color >> 8) & 0xFF) - g + MAX_RGB] +
                        SQUARES[((color >> 0) & 0xFF) - b + MAX_RGB]);
            }

            public String toString() {
                StringBuffer buf = new StringBuffer();
                if (parent == this) {
                    buf.append("root");
                } else {
                    buf.append("node");
                }
                buf.append(' ');
                buf.append(level);
                buf.append(" [");
                buf.append(mid_red);
                buf.append(',');
                buf.append(mid_green);
                buf.append(',');
                buf.append(mid_blue);
                buf.append(']');
                return new String(buf);
            }
        }
    }
}


// **********************************************************************
//
// <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 'removeable' to designate this layer as removeable
     * from the application, or able to be deleted. True by default.
     */
    public static final String RemoveableProperty = "removeable";

    /**
     * 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 removeable or not.
     */
    protected boolean removeable = 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));

        setRemoveable(PropUtils.booleanFromProperties(props, realPrefix
                + RemoveableProperty, removeable));

        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 + RemoveableProperty,
                new Boolean(removeable).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,
                RemoveableProperty,
                I18n.TOOLTIP,
                "Flag to allow layer to be deleted.");
        list.put(RemoveableProperty, internString);
        internString = i18n.get(Layer.class, RemoveableProperty, "Removeable");
        list.put(RemoveableProperty + LabelEditorProperty, internString);
        list.put(RemoveableProperty + 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.
     *
     * _at_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.
     *
     * _at_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.
     *
     * _at_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.
     *
     * _at_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.
     * _at_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.
     *
     * _at_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 removeable, or one that can be deleted from
     * the application. What that means is up to the LayerHandler or
     * other application components.
     */
    public void setRemoveable(boolean set) {
        removeable = set;
    }

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

    /**
     * Check to see if the removeable 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);
        }
    }
}

--
[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 Tue Jan 25 2005 - 12:16:02 EST

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