edu.wisc.ssec.mcidasv.control.cyclone.StormDisplayState.java Source code

Java tutorial

Introduction

Here is the source code for edu.wisc.ssec.mcidasv.control.cyclone.StormDisplayState.java

Source

/*
 * This file is part of McIDAS-V
 *
 * Copyright 2007-2015
 * Space Science and Engineering Center (SSEC)
 * University of Wisconsin - Madison
 * 1225 W. Dayton Street, Madison, WI 53706, USA
 * http://www.ssec.wisc.edu/mcidas
 * 
 * All Rights Reserved
 * 
 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
 * some McIDAS-V source code is based on IDV and VisAD source code.  
 * 
 * McIDAS-V is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * McIDAS-V is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser Public License
 * along with this program.  If not, see http://www.gnu.org/licenses.
 */

package edu.wisc.ssec.mcidasv.control.cyclone;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.FileOutputStream;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.JTableHeader;
import javax.swing.tree.DefaultTreeCellRenderer;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.w3c.dom.Element;

import ucar.unidata.data.gis.KmlUtil;
import ucar.unidata.data.storm.StormInfo;
import ucar.unidata.data.storm.StormParam;
import ucar.unidata.data.storm.StormTrack;
import ucar.unidata.data.storm.StormTrackCollection;
import ucar.unidata.data.storm.StormTrackPoint;
import ucar.unidata.data.storm.Way;
import ucar.unidata.geoloc.LatLonPointImpl;
import ucar.unidata.geoloc.LatLonRect;
import ucar.unidata.idv.control.ColorTableWidget;
import ucar.unidata.idv.control.LayoutModelWidget;
import ucar.unidata.idv.flythrough.FlythroughPoint;
import ucar.unidata.ui.Command;
import ucar.unidata.ui.CommandManager;
import ucar.unidata.ui.TableSorter;
import ucar.unidata.ui.TreePanel;
import ucar.unidata.ui.colortable.ColorTableCanvas;
import ucar.unidata.ui.drawing.Glyph;
import ucar.unidata.ui.symbol.ShapeSymbol;
import ucar.unidata.ui.symbol.StationModel;
import ucar.unidata.ui.symbol.StationModelManager;
import ucar.unidata.util.ColorTable;
import ucar.unidata.util.DateUtil;
import ucar.unidata.util.FileManager;
import ucar.unidata.util.GuiUtils;
import ucar.unidata.util.IOUtil;
import ucar.unidata.util.Misc;
import ucar.unidata.util.PatternFileFilter;
import ucar.unidata.util.Range;
import ucar.visad.display.Animation;
import ucar.visad.display.CompositeDisplayable;
import ucar.visad.display.DisplayMaster;
import ucar.visad.display.Displayable;
import ucar.visad.display.DisplayableData;
import ucar.visad.display.LineDrawing;
import visad.CommonUnit;
import visad.Data;
import visad.DateTime;
import visad.DisplayEvent;
import visad.Real;
import visad.RealType;
import visad.Set;
import visad.Unit;
import visad.VisADException;
import visad.georef.EarthLocation;
import visad.georef.EarthLocationLite;
import visad.georef.LatLonPoint;

/**
 * Part of the McV/IDV implementation of AODT
 * @author Unidata Development Team, McIDAS-V Development Team
 * @version $Revision$
 */

public class StormDisplayState {

    /** _more_ */
    public static final String PROP_TRACK_TABLE = "prop.track.table";

    /** _more_ */
    private static String ID_OBS_CONE = "id.obs.cone";

    /** _more_ */
    private static String ID_OBS_RINGS = "id.obs.rings";

    /** _more_ */
    private static String ID_OBS_LAYOUTMODEL = "id.obs.layoutmodel";

    /** _more_ */
    private static String ID_FORECAST_CONE = "id.forecast.cone";

    /** _more_ */
    private static String ID_FORECAST_RINGS = "id.forecast.rings";

    /** _more_ */
    private static String ID_FORECAST_COLOR = "id.forecast.color";

    /** _more_ */
    private static String ID_FORECAST_LAYOUTMODEL = "id.forecast.layoutmodel";

    /** _more_ */
    private static String ID_OBS_COLOR = "id.obs.color";

    /** The array of colors we cycle through */
    private static Color[] colors = { Color.RED, Color.PINK, Color.MAGENTA, Color.ORANGE, Color.YELLOW, Color.GREEN,
            Color.BLUE, Color.CYAN, Color.GRAY, Color.LIGHT_GRAY };

    /** _more_ */
    private boolean hasBeenEdited = false;

    /** _more_ */
    private boolean colorRangeChanged = false;

    /** _more_ */
    private static int[] nextColor = { 0 };

    /** _more_ */
    private JLabel obsColorTableLabel;

    /** _more_ */
    private JLabel forecastColorTableLabel;

    /** _more_ */
    private List<StormTrackChart> charts = new ArrayList<StormTrackChart>();

    /** _more_ */
    private List<StormTrackTableModel> tableModels = new ArrayList<StormTrackTableModel>();

    /** _more_ */
    private TreePanel tableTreePanel;

    /** _more_ */
    private Object MUTEX = new Object();

    /** _more_ */
    private static final Data DUMMY_DATA = new Real(0);

    /** _more_ */
    private CompositeDisplayable holder;

    /** _more_ */
    private boolean isOnlyChild = false;

    /** _more_ */
    private StormInfo stormInfo;

    /** _more_ */
    private WayDisplayState forecastState;

    /** _more_ */
    private boolean haveLoadedForecasts = false;

    /** _more_ */
    private boolean changed = false;

    /** _more_ */
    private boolean active = false;

    /** _more_ */
    private StormTrackCollection trackCollection;

    /** _more_ */
    private StormTrackControl stormTrackControl;

    /** _more_ */
    private WayDisplayState obsDisplayState;

    /** _more_ */
    private String obsLayoutModelName = "Storm>Hurricane";

    /** _more_ */
    private String obsPointLayoutModelName = "Storm>Forecast Hour";

    /** _more_ */
    private String forecastLayoutModelName = "Storm>Forecast Hour";

    /** time holder */
    private DisplayableData timesHolder = null;

    /** _more_ */
    private JComponent mainContents;

    /** _more_ */
    private JTabbedPane tabbedPane;

    /** _more_ */
    private JComponent originalContents;

    /** _more_ */
    private Hashtable params = new Hashtable();

    /** _more_ */
    private static final int FORECAST_TIME_MODE = 0;

    /** _more_ */
    private int forecastAnimationMode = FORECAST_TIME_MODE;

    /** _more_ */
    private JComboBox timeModeBox;

    /** _more_ */
    private Hashtable<Way, WayDisplayState> wayDisplayStateMap = new Hashtable<Way, WayDisplayState>();

    /** _more_ */
    private CommandManager commandManager;

    /**
     * _more_
     */
    public StormDisplayState() {
    }

    /**
     * _more_
     * 
     * @param stormInfo
     *            _more_
     * 
     * @throws Exception
     *             _more_
     */
    public StormDisplayState(StormInfo stormInfo) throws Exception {
        this.stormInfo = stormInfo;
        forecastState = new WayDisplayState(this, new Way("forecaststate"));
        forecastState.getWayState().setVisible(false);
        forecastState.getConeState().setVisible(true);
        forecastState.getTrackState().setVisible(true);
        forecastState.getRingsState().setVisible(true);
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    private CommandManager getCommandManager() {
        if (commandManager == null) {
            commandManager = new CommandManager(100);
        }
        return commandManager;
    }

    /**
     * _more_
     */
    private void checkVisibility() {
        List<WayDisplayState> wayDisplayStates = getWayDisplayStates();
        Color bgcolor = Color.lightGray;

        boolean rowOk = forecastState.getWayState().getVisible();
        forecastState.getRingsState().setBackground(rowOk ? null : bgcolor);
        forecastState.getConeState().setBackground(rowOk ? null : bgcolor);
        forecastState.getTrackState().setBackground(rowOk ? null : bgcolor);
        for (WayDisplayState wds : wayDisplayStates) {
            rowOk = wds.getWayState().getVisible();
            if (wds.getWay().isObservation()) {
                wds.getRingsState().setBackground(rowOk ? null : bgcolor);
                wds.getConeState().setBackground(rowOk ? null : bgcolor);
                wds.getTrackState().setBackground(rowOk ? null : bgcolor);
            } else {
                rowOk = rowOk && forecastState.getWayState().getVisible();
                wds.getWayState().setBackground(forecastState.getWayState().getVisible() ? null : bgcolor);
                wds.getRingsState()
                        .setBackground((rowOk && forecastState.getRingsState().getVisible()) ? null : bgcolor);
                wds.getConeState()
                        .setBackground((rowOk && forecastState.getConeState().getVisible()) ? null : bgcolor);
                wds.getTrackState()
                        .setBackground((rowOk && forecastState.getTrackState().getVisible()) ? null : bgcolor);
            }
        }
    }

    /**
     * _more_
     */
    public void colorTableChanged() {
        try {
            updateDisplays();
        } catch (Exception exc) {
            stormTrackControl.logException("Changing color table", exc);
        }
    }

    /** _more_ */
    private int wayCnt = -1;

    /** _more_ */
    private StormTrack editedStormTrack;

    /** _more_ */
    private StormTrackPoint editedStormTrackPoint;

    /**
     * _more_
     * 
     * @param event
     *            _more_
     * 
     * @throws Exception
     *             _more_
     */
    public void handleEvent(DisplayEvent event) throws Exception {
        int id = event.getId();
        InputEvent inputEvent = event.getInputEvent();
        if ((inputEvent instanceof KeyEvent)) {
            KeyEvent keyEvent = (KeyEvent) inputEvent;
            if ((keyEvent.getKeyCode() == KeyEvent.VK_Z) && keyEvent.isControlDown()) {
                getCommandManager().undo();
                return;
            }
            if ((keyEvent.getKeyCode() == KeyEvent.VK_Y) && keyEvent.isControlDown()) {
                getCommandManager().redo();
                return;
            }
        }

        EarthLocation el = stormTrackControl.toEarth(event);
        LatLonPoint llp = el.getLatLonPoint();

        if (id == DisplayEvent.MOUSE_PRESSED) {
            List<StormDisplayState> me = new ArrayList<StormDisplayState>();
            me.add(this);

            // System.err.println ("looking");
            Real animationTime = null;
            Animation animation = stormTrackControl.getViewAnimation();
            if (animation != null) {
                animationTime = animation.getAniValue();
            }
            if (animationTime == null) {
                // System.err.println ("no animation");
                return;
            }
            Object[] tuple = stormTrackControl.findClosestPoint(el, me, animationTime, 50);
            if (tuple == null) {
                // System.err.println ("nothing found");
                return;
            }
            editedStormTrack = (StormTrack) tuple[0];
            editedStormTrackPoint = (StormTrackPoint) tuple[1];
        }

        if (id == DisplayEvent.MOUSE_DRAGGED) {
            if (editedStormTrackPoint == null) {
                return;
            }
            handleMouseDrag(event, el);
        }

        if (id == DisplayEvent.MOUSE_RELEASED) {
            editedStormTrackPoint = null;
            editedStormTrack = null;
        }
    }

    /**
     * Class PointEditCommand _more_
     * 
     * 
     * @author IDV Development Team
     */
    private class PointEditCommand extends Command {

        /** _more_ */
        StormTrack stormTrack;

        /** _more_ */
        List<StormTrackPoint> originalPoints;

        /** _more_ */
        List<StormTrackPoint> newPoints;

        /**
         * _more_
         * 
         * @param stormTrack
         *            _more_
         * @param originalPoints
         *            _more_
         * @param newPoints
         *            _more_
         */
        public PointEditCommand(StormTrack stormTrack, List<StormTrackPoint> originalPoints,
                List<StormTrackPoint> newPoints) {
            this.stormTrack = stormTrack;
            this.originalPoints = originalPoints;
            this.newPoints = newPoints;
        }

        /**
         * _more_
         */
        public void redoCommand() {
            try {
                stormTrack.setTrackPoints(newPoints);
                updateDisplays(stormTrack);
            } catch (Exception exp) {
                stormTrackControl.logException("undoing edit command", exp);
            }
        }

        /**
         * Undo
         */
        public void undoCommand() {
            try {
                stormTrack.setTrackPoints(originalPoints);
                updateDisplays(stormTrack);
            } catch (Exception exp) {
                stormTrackControl.logException("undoing edit command", exp);
            }
        }

    }

    /**
     * _more_
     * 
     * @param event
     *            _more_
     * @param newPt
     *            _more_
     * 
     * @throws Exception
     *             _more_
     */
    private void handleMouseDrag(DisplayEvent event, EarthLocation newPt) throws Exception {
        List<StormTrackPoint> points = editedStormTrack.getTrackPoints();
        List<StormTrackPoint> originalPoints = new ArrayList<StormTrackPoint>();
        for (StormTrackPoint stp : points) {
            originalPoints.add(new StormTrackPoint(stp));
        }

        // if the control key is not down then just move the point
        int stretchIndex = editedStormTrack.indexOf(editedStormTrackPoint);
        if (stretchIndex < 0) {
            // this should never happen
            throw new IllegalStateException("Cannot find track point");
        }

        EarthLocation oldPt = (EarthLocation) points.get(stretchIndex).getLocation();

        double deltaY = oldPt.getLatitude().getValue(CommonUnit.degree)
                - newPt.getLatitude().getValue(CommonUnit.degree);
        double deltaX = LatLonPointImpl.lonNormal(oldPt.getLongitude().getValue(CommonUnit.degree))
                - LatLonPointImpl.lonNormal(newPt.getLongitude().getValue(CommonUnit.degree));

        if ((event.getModifiers() & event.CTRL_MASK) != 0) {
            editedStormTrackPoint.setLocation(newPt);
            // else do an interpolated stretch
            int startPts = stretchIndex - 1;
            int endPts = points.size() - stretchIndex;
            double percent = 1.0;

            // System.err.println("delta: " + deltaX + " " + deltaY);
            for (int i = stretchIndex - 1; i >= 0; i--) {
                percent -= 1.0 / (double) startPts;
                if (percent <= 0.05) {
                    break;
                }
                EarthLocation pt = (EarthLocation) points.get(i).getLocation();
                EarthLocation newEl = makePoint(pt.getLatitude().getValue(CommonUnit.degree) - deltaY * percent,
                        LatLonPointImpl.lonNormal(pt.getLongitude().getValue(CommonUnit.degree))
                                - deltaX * percent);
                // System.err.println("   " +percent + " " + pt.getLatLonPoint()
                // + " " + newEl.getLatLonPoint());
                points.get(i).setLocation(newEl);
            }
            percent = 1.0;
            for (int i = stretchIndex + 1; i < points.size(); i++) {
                percent -= 1.0 / (double) endPts;
                if (percent <= 0.05) {
                    break;
                }
                EarthLocation pt = (EarthLocation) points.get(i).getLocation();
                EarthLocation newEl = makePoint(pt.getLatitude().getValue(CommonUnit.degree) - deltaY * percent,
                        LatLonPointImpl.lonNormal(pt.getLongitude().getValue(CommonUnit.degree))
                                - deltaX * percent);
                points.get(i).setLocation(newEl);
            }
        } else if ((event.getModifiers() & event.SHIFT_MASK) != 0) {
            for (StormTrackPoint stp : points) {
                EarthLocation pt = (EarthLocation) stp.getLocation();
                EarthLocation newEl = makePoint(pt.getLatitude().getValue(CommonUnit.degree) - deltaY,
                        LatLonPointImpl.lonNormal(pt.getLongitude().getValue(CommonUnit.degree)) - deltaX);
                stp.setLocation(newEl);
            }
        } else {
            editedStormTrackPoint.setLocation(newPt);
        }

        getCommandManager()
                .add(new PointEditCommand(editedStormTrack, originalPoints, editedStormTrack.getTrackPoints()));
        updateDisplays(editedStormTrack);
    }

    /**
     * _more_
     * 
     * @param latitude
     *            _more_
     * @param longitude
     *            _more_
     * 
     * @return _more_
     * 
     * @throws RemoteException
     *             _more_
     * @throws VisADException
     *             _more_
     */
    protected EarthLocation makePoint(double latitude, double longitude) throws VisADException, RemoteException {
        Real altReal = new Real(RealType.Altitude, 0);
        return new EarthLocationLite(new Real(RealType.Latitude, latitude), new Real(RealType.Longitude, longitude),
                altReal);
    }

    /**
     * Check if its ok to show the given way. if we have less than 2 ways total
     * then always showit
     * 
     * @param way
     *            _more_
     * 
     * @return _more_
     */
    protected boolean okToShowWay(Way way) {
        if (wayCnt == -1) {

            List<StormTrack> tracks = trackCollection.getTracks();
            Hashtable ways = new Hashtable();
            wayCnt = 0;
            for (StormTrack track : tracks) {
                if (ways.get(track.getWay()) == null) {
                    wayCnt++;
                    ways.put(track.getWay(), "");
                }
            }
        }
        if (wayCnt <= 1) {
            return true;
        }
        return stormTrackControl.okToShowWay(way);
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    public LatLonRect getBoundingBox() {
        if (trackCollection == null) {
            return null;
        }
        double minLon = Double.POSITIVE_INFINITY;
        double maxLon = Double.NEGATIVE_INFINITY;
        double minLat = Double.POSITIVE_INFINITY;
        double maxLat = Double.NEGATIVE_INFINITY;

        boolean didone = false;
        List<StormTrack> tracks = trackCollection.getTracks();
        for (StormTrack track : tracks) {
            if (!okToShowWay(track.getWay())) {
                continue;
            }
            LatLonRect bbox = track.getBoundingBox();
            if (bbox == null) {
                continue;
            }
            minLon = Math.min(minLon, bbox.getLonMin());
            maxLon = Math.max(maxLon, bbox.getLonMax());
            minLat = Math.min(minLat, bbox.getLatMin());
            maxLat = Math.max(maxLat, bbox.getLatMax());
            didone = true;
        }
        if (!didone) {
            return null;
        }
        return new LatLonRect(new LatLonPointImpl(maxLat, minLon), new LatLonPointImpl(minLat, maxLon));
    }

    /**
     * _more_
     * 
     * @param isOnlyChild
     *            _more_
     */
    protected void setIsOnlyChild(boolean isOnlyChild) {
        this.isOnlyChild = isOnlyChild;
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    public JComponent getContents() {
        if (mainContents == null) {
            mainContents = doMakeContents();
        }
        return mainContents;
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    protected List<WayDisplayState> getWayDisplayStates() {
        return (List<WayDisplayState>) Misc.toList(wayDisplayStateMap.elements());
    }

    /**
     * _more_
     * 
     * @param way
     *            _more_
     * 
     * @return _more_
     */
    protected WayDisplayState getWayDisplayState(Way way) {
        WayDisplayState wayState = wayDisplayStateMap.get(way);
        if (wayState == null) {
            wayDisplayStateMap.put(way, wayState = new WayDisplayState(this, way));
            // "idv.stormtrackcontrol.way.color"
            if (wayState.getColor() == null) {
                wayState.setColor(getNextColor(nextColor));
            }
        }
        return wayState;
    }

    /**
     * _more_
     */
    protected void reload() {
        if (!active) {
            return;
        }
        deactivate();
        loadStorm();
    }

    /**
     * _more_
     */
    public void loadStorm() {
        if (active) {
            return;
        }
        active = true;
        showStorm();
    }

    /**
     * _more_
     */
    protected void reloadChart() {
        for (StormTrackChart stormTrackChart : charts) {
            // stormTrackChart.deactivate();
            stormTrackChart.updateChart();
        }
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    protected StormTrackCollection getTrackCollection() {
        return trackCollection;
    }

    /**
     * _more_
     * 
     * @param sm
     *            _more_
     */
    public void setObsLayoutModel(StationModel sm) {
        obsLayoutModelName = ((sm == null) ? null : sm.getName());
        updateLayoutModel(true);
    }

    /**
     * _more_
     * 
     * @param sm
     *            _more_
     */
    public void setObsPointLayoutModel(StationModel sm) {
        obsPointLayoutModelName = ((sm == null) ? null : sm.getName());
        updateLayoutModel(true);
    }

    /**
     * _more_
     * 
     * @param sm
     *            _more_
     */
    public void setForecastLayoutModel(StationModel sm) {
        forecastLayoutModelName = ((sm == null) ? null : sm.getName());
        updateLayoutModel(false);
    }

    /**
     * _more_
     * 
     * @param name
     *            _more_
     */
    protected void handleChangedStationModel(String name) {
        if (Misc.equals(obsLayoutModelName, name)) {
            updateLayoutModel(true);
        }
        if (Misc.equals(forecastLayoutModelName, name)) {
            updateLayoutModel(false);
        }
    }

    /**
     * _more_
     * 
     * @param forObs
     *            _more_
     */
    public void updateLayoutModel(boolean forObs) {
        List<WayDisplayState> wayDisplayStates = getWayDisplayStates();
        try {
            for (WayDisplayState wds : wayDisplayStates) {
                if (wds.getWay().isObservation() && !forObs) {
                    continue;
                }
                wds.updateLayoutModel();
            }
        } catch (Exception exc) {
            stormTrackControl.logException("Updating layout models", exc);
        }
    }

    /**
     * _more_
     */
    public void deactivate() {
        try {
            for (StormTrackChart stormTrackChart : charts) {
                stormTrackChart.deactivate();
            }
            trackCollection = null;
            active = false;
            colorRangeChanged = false;
            stormTrackControl.removeDisplayable(holder);
            holder = null;
            if (mainContents != null) {
                mainContents.removeAll();
                mainContents.add(BorderLayout.NORTH, originalContents);
                List<WayDisplayState> wayDisplayStates = getWayDisplayStates();
                for (WayDisplayState wayDisplayState : wayDisplayStates) {
                    wayDisplayState.deactivate();
                }
                mainContents.repaint(1);
            }
            stormTrackControl.stormChanged(StormDisplayState.this);

        } catch (Exception exc) {
            stormTrackControl.logException("Deactivating storm", exc);
        }
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    private JComponent doMakeContents() {
        JButton loadBtn = new JButton("Load Tracks:");
        JLabel topLabel = GuiUtils.cLabel("  " + stormInfo);
        loadBtn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                loadStorm();
            }
        });

        JComponent top = GuiUtils.hbox(loadBtn, topLabel);
        originalContents = GuiUtils.inset(top, 5);
        JComponent contents = GuiUtils.top(originalContents);
        final int cnt = xcnt++;
        contents = new JPanel(new BorderLayout());
        contents.add(BorderLayout.NORTH, originalContents);
        return contents;
    }

    /** _more_ */
    static int xcnt = 0;

    /**
     * _more_
     */
    public void initDone() {
        if (getActive()) {
            showStorm();
        }

    }

    /**
     * _more_
     * 
     * @return _more_
     */
    public boolean getForecastVisible() {
        // return forecastState.getVisible();
        return forecastState.getWayState().getVisible();
    }

    /**
     * _more_
     * 
     * @param stormParams
     *            _more_
     * @param id
     *            _more_
     * 
     * @return _more_
     */
    private JComponent makeList(List stormParams, final Object id) {
        if ((stormParams == null) || (stormParams.size() == 0)) {
            return GuiUtils.filler(2, 10);
        }
        final JList list = new JList(new Vector(stormParams));
        list.setVisibleRowCount(3);
        list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        list.addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                List<StormParam> selected = new ArrayList<StormParam>();
                selected.addAll(Misc.toList(list.getSelectedValues()));
                try {
                    params.put(id, selected);
                    updateDisplays();
                } catch (Exception exc) {
                    stormTrackControl.logException("setting cones", exc);
                }

            }
        });

        list.setToolTipText("<html>Parameter used for cone<br>Control-click for multiple select</html>");
        List selected = (List) params.get(id);
        if ((selected != null) && (selected.size() > 0)) {
            int[] indices = new int[selected.size()];
            for (int i = 0; i < selected.size(); i++) {
                indices[i] = stormParams.indexOf(selected.get(i));
            }
            list.setSelectedIndices(indices);
        }

        JScrollPane sp = new JScrollPane(list);
        return sp;
    }

    /**
     * _more_
     * 
     * @param stormParams
     *            _more_
     * @param id
     *            _more_
     * @param tooltip
     *            _more_
     * 
     * @return _more_
     */
    private JComponent makeBox(List stormParams, final Object id, String tooltip) {
        if ((stormParams == null) || (stormParams.size() == 0)) {
            return GuiUtils.filler(2, 10);
        }
        final JComboBox box = new JComboBox(new Vector(stormParams));
        box.setToolTipText(tooltip);
        StormParam stormParam = (StormParam) params.get(id);
        if (stormParam != null) {
            box.setSelectedItem(stormParam);
        }
        box.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                Object selected = box.getSelectedItem();
                if ((selected == null) || (selected instanceof String)) {
                    params.remove(id);
                } else {
                    params.put(id, selected);
                }
                try {
                    colorRangeChanged = false;
                    updateDisplays();
                } catch (Exception exc) {
                    stormTrackControl.logException("setting cones", exc);
                }

            }
        });

        return box;
    }

    /**
     * _more_
     * 
     * @param params
     *            _more_
     * 
     * @return _more_
     */
    private List<StormParam> getDistanceParams(List<StormParam> params) {
        if ((params == null) || (params.size() == 0)) {
            return null;
        }

        List<StormParam> attrNames = new ArrayList<StormParam>();
        for (StormParam param : params) {
            if (Unit.canConvert(param.getUnit(), CommonUnit.meter)) {

                attrNames.add(param);
            }

        }
        if (attrNames.size() == 0) {
            return null;
        }
        return attrNames;
    }

    // RealType fixedtype;
    StormParam getFixedParam() {
        RealType rtype = RealType.getRealType("Fixed");
        //      if (rtype == null) {
        //         try {
        //            rtype = new RealType("Fixed");
        //         } catch (VisADException e) {
        //
        //         }
        //         // fixedtype=rtype;
        //      }
        return new StormParam(rtype, false, false);
    }

    /**
     * _more_
     */
    private void initCenterContents() {

        if (mainContents == null) {
            return;
        }

        mainContents.removeAll();
        JButton unloadBtn = GuiUtils.makeImageButton("/auxdata/ui/icons/Cut16.gif", this, "deactivate");
        unloadBtn.setToolTipText("Remove this storm");
        String label = "Storm: " + stormInfo.toString() + "   "
                + stormInfo.getStartTime().formattedString("yyyy-MM-dd", DateUtil.TIMEZONE_GMT);

        JComponent top = GuiUtils.inset(GuiUtils.leftRight(GuiUtils.lLabel(label), unloadBtn),
                new Insets(0, 0, 0, 0));

        List<StormParam> forecastParams = new ArrayList<StormParam>();
        Hashtable seenParams = new Hashtable();
        List<StormParam> obsParams = new ArrayList<StormParam>();
        Hashtable seenWays = new Hashtable();
        for (StormTrack track : trackCollection.getTracks()) {
            // if (seenWays.get(track.getWay()) != null) {
            // continue;
            // }
            // seenWays.put(track.getWay(), track.getWay());
            List<StormParam> trackParams = track.getParams();
            if (track.getWay().isObservation()) {
                obsParams.addAll(trackParams);
                continue;
            }
            for (StormParam param : trackParams) {
                if (seenParams.get(param) != null) {
                    continue;
                }
                seenParams.put(param, param);
                forecastParams.add(param);
            }
        }

        List<StormParam> forecastRadiusParams = getDistanceParams(forecastParams);
        List<StormParam> obsRadiusParams = getDistanceParams(obsParams);

        if (obsRadiusParams != null) {
            // If its not set then set it
            if (params.get(ID_OBS_RINGS) == null) {
                params.put(ID_OBS_RINGS, obsRadiusParams.get(0));
            }
            if (params.get(ID_OBS_CONE) == null) {
                params.put(ID_OBS_CONE, Misc.newList(obsRadiusParams.get(0)));
            }
        }
        if (forecastRadiusParams != null) {
            // If its not set then set it
            if (params.get(ID_FORECAST_RINGS) == null) {
                params.put(ID_FORECAST_RINGS, forecastRadiusParams.get(0));
            }
            if (params.get(ID_FORECAST_CONE) == null) {
                params.put(ID_FORECAST_CONE, Misc.newList(forecastRadiusParams.get(0)));
            }
        }

        // Sort them by name

        List<Way> ways = Misc.sort(trackCollection.getWayList());
        boolean haveDoneForecast = false;
        List<String> colLabels = (List<String>) Misc.newList("", "Show", "Track");
        if ((forecastRadiusParams != null) || (obsRadiusParams != null)) {
            colLabels.add("Rings");
            colLabels.add("Cone");
        }
        int numCols = colLabels.size();

        List obsColorParams = new ArrayList(obsParams);
        List forecastColorParams = new ArrayList(forecastParams);
        obsColorParams.add(0, getFixedParam());
        forecastColorParams.add(0, getFixedParam());

        JComponent obsLayoutComp = new LayoutModelWidget(stormTrackControl, this, "setObsLayoutModel",
                getObsLayoutModel(), true);
        JComponent obsPointLayoutComp = new LayoutModelWidget(stormTrackControl, this, "setObsPointLayoutModel",
                getObsPointLayoutModel(), true);
        JComponent forecastLayoutComp = new LayoutModelWidget(stormTrackControl, this, "setForecastLayoutModel",
                getForecastLayoutModel(), true);

        JComponent obsColorByBox = makeBox(obsColorParams, ID_OBS_COLOR,
                "Parameter used for coloring observation track");
        JComponent forecastColorByBox = makeBox(forecastColorParams, ID_FORECAST_COLOR,
                "Parameter used for coloring forecast tracks");

        JComponent obsConeComp = ((obsRadiusParams != null) ? makeList(obsRadiusParams, ID_OBS_CONE)
                : (JComponent) GuiUtils.filler());
        JComponent obsRingComp = ((obsRadiusParams != null)
                ? makeBox(obsRadiusParams, ID_OBS_RINGS, "Parameter used for observation rings")
                : (JComponent) GuiUtils.filler());

        JComponent forecastConeComp = ((forecastRadiusParams != null)
                ? makeList(forecastRadiusParams, ID_FORECAST_CONE)
                : (JComponent) GuiUtils.filler());
        JComponent forecastRingComp = ((forecastRadiusParams != null)
                ? makeBox(forecastRadiusParams, ID_FORECAST_RINGS, "Parameter used for forecast rings")
                : (JComponent) GuiUtils.filler());

        List topComps = new ArrayList();

        timeModeBox = new JComboBox(new Vector(Misc.newList("On", "Off")));
        timeModeBox.setSelectedIndex(forecastAnimationMode);
        timeModeBox.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                forecastAnimationMode = timeModeBox.getSelectedIndex();
                try {
                    // reload();
                    updateDisplays();
                } catch (Exception exc) {
                    stormTrackControl.logException("change forecast animation mode", exc);
                }
            }
        });
        timeModeBox.setToolTipText("Animate tracks or show all tracks.");

        JComponent forecastModeComp = GuiUtils.inset(GuiUtils.left(GuiUtils.label("Animation Mode: ", timeModeBox)),
                5);

        topComps.add(new JLabel(""));
        topComps.add(GuiUtils.cLabel("<html><u><i>Observation</i></u></html>"));
        topComps.add(GuiUtils.cLabel("<html><u><i>Forecast</i></u></html>"));

        topComps.add(GuiUtils.rLabel("Points:"));
        topComps.add(obsPointLayoutComp);
        topComps.add(forecastLayoutComp);

        topComps.add(GuiUtils.rLabel("Animation:"));
        topComps.add(obsLayoutComp);
        topComps.add(forecastModeComp); // GuiUtils.filler());

        forecastColorTableLabel = new JLabel(" ");
        forecastColorTableLabel.setToolTipText("Color table preview");
        obsColorTableLabel = new JLabel(" ");
        obsColorTableLabel.setToolTipText("Color table preview");

        topComps.add(GuiUtils.rLabel("Color By:"));
        topComps.add(GuiUtils.vbox(obsColorByBox, obsColorTableLabel));
        topComps.add(GuiUtils.vbox(forecastColorByBox, forecastColorTableLabel));

        if ((forecastRadiusParams != null) || (obsRadiusParams != null)) {
            topComps.add(GuiUtils.rLabel("Rings:"));
            topComps.add(obsRingComp);
            topComps.add(forecastRingComp);
            topComps.add(GuiUtils.rLabel("Cone:"));
            topComps.add(obsConeComp);
            topComps.add(forecastConeComp);
        }

        GuiUtils.tmpInsets = new Insets(4, 4, 2, 2);
        JComponent paramComp = GuiUtils.doLayout(topComps, 3, GuiUtils.WT_N, GuiUtils.WT_N);

        List comps = new ArrayList();

        for (Way way : ways) {
            WayDisplayState wds = getWayDisplayState(way);
            if (!okToShowWay(wds.getWay())) {
                continue;
            }
            JComponent labelComp = GuiUtils.hbox(wds.getWayState().getCheckBox(), new JLabel(" " + way.toString()));

            JComponent swatch = GuiUtils.wrap(wds.getColorSwatch());
            if (way.isObservation()) {
                // We put the obs in the front of the list
                int col = 0;
                comps.add(col++, swatch);
                comps.add(col++, labelComp);
                comps.add(col++, GuiUtils.wrap(wds.getTrackState().getCheckBox()));
                if (obsRadiusParams != null) {
                    comps.add(col++, GuiUtils.wrap(wds.getRingsState().getCheckBox()));
                    comps.add(col++, GuiUtils.wrap(wds.getConeState().getCheckBox()));
                }

            } else {
                if (!haveDoneForecast) {

                    // Put the forecast info here
                    haveDoneForecast = true;
                    for (int colIdx = 0; colIdx < numCols; colIdx++) {
                        comps.add(GuiUtils.filler());
                    }

                    comps.add(GuiUtils.filler());
                    comps.add(GuiUtils.hbox(forecastState.getWayState().getCheckBox(),
                            GuiUtils.lLabel("<html><u><i>Forecasts:</i></u></html>")));
                    comps.add(GuiUtils.wrap(forecastState.getTrackState().getCheckBox()));
                    if (forecastRadiusParams != null) {
                        comps.add(GuiUtils.wrap(forecastState.getRingsState().getCheckBox()));

                        comps.add(GuiUtils.wrap(forecastState.getConeState().getCheckBox()));
                    }
                }
                comps.add(swatch);
                comps.add(labelComp);
                comps.add(GuiUtils.wrap(wds.getTrackState().getCheckBox()));
                if (forecastRadiusParams != null) {
                    comps.add(GuiUtils.wrap(wds.getRingsState().getCheckBox()));
                    comps.add(GuiUtils.wrap(wds.getConeState().getCheckBox()));
                }
            }
        }

        for (int colIdx = 0; colIdx < numCols; colIdx++) {
            String s = colLabels.get(colIdx);
            if (s.length() > 0) {
                comps.add(colIdx, new JLabel("<html><u><i>" + s + "</i></u></html>"));
            } else {
                comps.add(colIdx, new JLabel(""));
            }
        }

        GuiUtils.tmpInsets = new Insets(2, 2, 0, 2);
        JComponent wayComp = GuiUtils.topLeft(GuiUtils.doLayout(comps, numCols, GuiUtils.WT_N, GuiUtils.WT_N));
        // Put the list of ways into a scroller if there are lots of them
        if (ways.size() > 6) {
            int width = 300;
            int height = 200;
            JScrollPane scroller = GuiUtils.makeScrollPane(wayComp, width, height);
            scroller.setBorder(BorderFactory.createLoweredBevelBorder());
            scroller.setPreferredSize(new Dimension(width, height));
            scroller.setMinimumSize(new Dimension(width, height));
            wayComp = scroller;
        }

        wayComp = GuiUtils.left(GuiUtils.doLayout(
                new Component[] { GuiUtils.left(paramComp), GuiUtils.filler(2, 10), GuiUtils.left(wayComp) }, 1,
                GuiUtils.WT_N, GuiUtils.WT_NNY));

        wayComp = GuiUtils.inset(wayComp, new Insets(0, 5, 0, 0));
        // tabbedPane = GuiUtils.getNestedTabbedPane();
        tabbedPane = new JTabbedPane();
        tabbedPane.addTab("Tracks", wayComp);
        tabbedPane.addTab("Table", getTrackTable());

        if (charts.size() == 0) {
            charts.add(new StormTrackChart(this, "Storm Chart", StormTrackChart.MODE_FORECASTTIME));
        }
        for (StormTrackChart stormTrackChart : charts) {
            tabbedPane.addTab(stormTrackChart.getName(), stormTrackChart.getContents());
        }

        JComponent inner = GuiUtils.topCenter(top, tabbedPane);
        inner = GuiUtils.inset(inner, 5);
        mainContents.add(BorderLayout.CENTER, inner);
        mainContents.invalidate();
        mainContents.validate();
        mainContents.repaint();

        checkVisibility();
    }

    /**
     * Class ParamSelector _more_
     * 
     * 
     * @author IDV Development Team
     * @version $Revision$
     */
    private static class ParamSelector {

        /** _more_ */
        List<StormParam> params;

        /** _more_ */
        JList list;

        /**
         * _more_
         * 
         * @param types
         *            _more_
         */
        public ParamSelector(List<StormParam> types) {
        }
    }

    /**
     * _more_
     * 
     * @param time
     *            _more_
     */
    protected void timeChanged(Real time) {
        for (StormTrackChart stormTrackChart : charts) {
            stormTrackChart.timeChanged(time);
        }
    }

    /**
     * _more_
     * 
     * @param way
     *            _more_
     * 
     * @return _more_
     */
    protected boolean canShowWay(Way way) {
        return getWayDisplayState(way).getWayState().getVisible();
    }

    /**
     * _more_
     * 
     * @param stormTrackControl
     *            _more_
     */
    protected void setStormTrackControl(StormTrackControl stormTrackControl) {
        this.stormTrackControl = stormTrackControl;
    }

    /**
     * _more_
     */
    protected void showStorm() {
        Misc.run(new Runnable() {
            public void run() {
                DisplayMaster displayMaster = stormTrackControl.getDisplayMaster();
                boolean wasActive = displayMaster.isActive();
                displayMaster.setDisplayInactive();
                try {
                    synchronized (MUTEX) {
                        stormTrackControl.showWaitCursor();
                        showStormInner();
                        stormTrackControl.stormChanged(StormDisplayState.this);
                    }
                } catch (Exception exc) {
                    stormTrackControl.logException("Showing storm", exc);
                } finally {
                    stormTrackControl.showNormalCursor();
                    if (wasActive) {
                        try {
                            //                     displayMaster.setActive(true);
                            displayMaster.setDisplayActive();
                        } catch (Exception exc) {
                        }
                    }
                }

            }
        });
    }

    /**
     * _more_
     * 
     * @param displayable
     *            _more_
     * 
     * @throws RemoteException
     *             _more_
     * @throws VisADException
     *             _more_
     */
    protected void addDisplayable(Displayable displayable) throws VisADException, RemoteException {
        if (holder != null) {
            holder.addDisplayable(displayable);
        }
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    protected StormTrackControl getStormTrackControl() {
        return stormTrackControl;
    }

    /**
     * _more_
     * 
     * 
     * @throws Exception
     *             _more_
     */
    private void showStormInner() throws Exception {

        // Read the tracks if we haven't
        long t1 = System.currentTimeMillis();
        if (trackCollection == null) {
            if (mainContents != null) {
                mainContents.removeAll();
                mainContents.add(GuiUtils.top(GuiUtils.inset(new JLabel("Loading Tracks..."), 5)));
                mainContents.invalidate();
                mainContents.validate();
                mainContents.repaint();
            }

            trackCollection = stormTrackControl.getStormDataSource().getTrackCollection(stormInfo,
                    stormTrackControl.getOkWays(), stormTrackControl.getObservationWay());
            initCenterContents();
            stormTrackControl.addDisplayable(holder = new CompositeDisplayable());
            // Add the tracks
            for (StormTrack track : trackCollection.getTracks()) {
                WayDisplayState wayDisplayState = getWayDisplayState(track.getWay());
                wayDisplayState.addTrack(track);
            }
            obsDisplayState = getWayDisplayState(Way.OBSERVATION);
            StormTrack obsTrack = trackCollection.getObsTrack();

            List<DateTime> times = new ArrayList<DateTime>();
            if (obsTrack != null) {
                times = obsTrack.getTrackTimes();
            } else {
                for (StormTrack track : trackCollection.getTracks()) {
                    times.add(track.getStartTime());
                }
            }
            if (times.size() > 0) {
                times = (List<DateTime>) Misc.sort(Misc.makeUnique(times));
                timesHolder = new LineDrawing("track_time" + stormInfo.getStormId());
                timesHolder.setManipulable(false);
                timesHolder.setVisible(false);
                Set timeSet = ucar.visad.Util.makeTimeSet(times);
                // System.err.println("time set:" + timeSet);
                timesHolder.setData(timeSet);
                holder.addDisplayable(timesHolder);
            }
        }

        updateDisplays();
        updateCharts();

        long t2 = System.currentTimeMillis();
        // System.err.println("time:" + (t2 - t1));
    }

    /**
     * _more_
     * 
     * @param id
     *            _more_
     * 
     * @return _more_
     */
    protected List<StormParam> getParams(Object id) {
        List<StormParam> l = (List<StormParam>) params.get(id);
        if (l == null) {
            l = new ArrayList<StormParam>();
            params.put(id, l);
        }
        return l;
    }

    /**
     * _more_
     * 
     * @param way
     *            _more_
     * 
     * @return _more_
     */
    protected List<StormParam> getConeParams(WayDisplayState way) {
        if (way.getWay().isObservation()) {
            return getParams(ID_OBS_CONE);
        }
        return getParams(ID_FORECAST_CONE);
    }

    /**
     * _more_
     * 
     * @param way
     *            _more_
     * 
     * @return _more_
     */
    protected StormParam getRingsParam(WayDisplayState way) {
        if (way.getWay().isObservation()) {
            return (StormParam) params.get(ID_OBS_RINGS);
        }
        return (StormParam) params.get(ID_FORECAST_RINGS);
    }

    /**
     * _more_
     * 
     * @param way
     *            _more_
     * 
     * @return _more_
     */
    protected StormParam getColorParam(WayDisplayState way) {
        return getColorParam(way.getWay().isObservation());
    }

    /**
     * _more_
     * 
     * @param forObs
     *            _more_
     * 
     * @return _more_
     */
    protected StormParam getColorParam(boolean forObs) {
        if (forObs) {
            StormParam sp = (StormParam) params.get(ID_OBS_COLOR);
            if (sp == null)
                sp = getFixedParam();
            return sp;
        }
        return (StormParam) params.get(ID_FORECAST_COLOR);
    }

    /**
     * _more_
     * 
     * @throws Exception
     *             _more_
     */
    protected void updateCharts() throws Exception {
        if (mainContents == null) {
            return;
        }

        for (StormTrackChart stormTrackChart : charts) {
            stormTrackChart.updateChart();
        }
    }

    /**
     * _more_
     * 
     * @param displayState
     *            _more_
     * 
     * @throws Exception
     *             _more_
     */
    protected void displayStateChanged(DisplayState displayState) throws Exception {
        updateDisplays();
        checkVisibility();
    }

    /**
     * _more_
     * 
     * @param track
     *            _more_
     * 
     * @throws Exception
     *             _more_
     */
    protected void updateDisplays(StormTrack track) throws Exception {
        Way way = track.getWay();
        WayDisplayState wds = wayDisplayStateMap.get(way);
        if (wds != null) {
            wds.updateDisplay(true);
            for (StormTrackTableModel trackModel : tableModels) {
                if (trackModel.getStormTrack().equals(track)) {
                    trackModel.fireTableStructureChanged();
                    Component comp = (Component) track.getTemporaryProperty(PROP_TRACK_TABLE);
                    track.setIsEdited(true);
                    if (comp != null) {
                        tableTreePanel.show(comp);
                        tableTreePanel.showPath(comp);
                    }
                    break;
                }
            }
        }
    }

    /**
     * _more_
     * 
     * @throws Exception
     *             _more_
     */
    protected void updateDisplays() throws Exception {
        updateDisplays(false);
    }

    // sstretch protected void updateDisplays() throws Exception {

    /**
     * _more_
     * 
     * @param force
     *            _more_
     * 
     * @throws Exception
     *             _more_
     */

    protected void updateDisplays(boolean force) throws Exception {
        DisplayMaster displayMaster = stormTrackControl.getDisplayMaster();
        boolean wasActive = displayMaster.isActive();
        displayMaster.setDisplayInactive();
        try {
            List<WayDisplayState> wayDisplayStates = getWayDisplayStates();
            for (WayDisplayState wds : wayDisplayStates) {
                if (!okToShowWay(wds.getWay())) {
                    continue;
                }
                wds.updateDisplay(force);
            }
        } finally {
            if (wasActive) {
                try {
                    //               displayMaster.setActive(true);
                    displayMaster.setDisplayActive();
                } catch (Exception exc) {
                }
            }
        }

        if (obsColorTableLabel != null) {
            ColorTable ct = null;

            ct = getColorTable(getColorParam(true));
            obsColorTableLabel.setIcon(((ct != null) ? ColorTableCanvas.getIcon(ct) : null));

            obsColorTableLabel.setToolTipText(getColorTableToolTip(true));

        }

        if (forecastColorTableLabel != null) {
            ColorTable ct = null;

            ct = getColorTable(getColorParam(false));
            forecastColorTableLabel.setIcon(((ct != null) ? ColorTableCanvas.getIcon(ct) : null));

            forecastColorTableLabel.setToolTipText(getColorTableToolTip(false));
        }

    }

    /**
     * _more_
     * 
     * @param forObs
     *            _more_
     * 
     * @return _more_
     */
    protected String getColorTableToolTip(boolean forObs) {
        StormParam param = getColorParam(forObs);
        if (param == null) {
            return "Color table preview";
        }
        Range range = getStormTrackControl().getIdv().getParamDefaultsEditor().getParamRange(param.getName());
        if (range == null) {
            return "Color table preview";
        }

        Unit displayUnit = getStormTrackControl().getIdv().getParamDefaultsEditor()
                .getParamDisplayUnit(param.getName());

        String unit = ((displayUnit != null) ? "[" + displayUnit + "]" : "");
        return "Range: " + range.getMin() + unit + " - " + range.getMax() + unit;
    }

    /**
     * _more_
     * 
     * @param param
     *            _more_
     * 
     * @return _more_
     */
    protected ColorTable getColorTable(StormParam param) {
        if (param == null) {

            return null;
        } else if (param.getName().equalsIgnoreCase("Fixed")) {
            try {
                getStormTrackControl().getColorTableWidget(new Range(1.0, 1.0));
            } catch (VisADException r) {
            } catch (RemoteException s) {
            }
            return null;
        }

        Range range = getStormTrackControl().getIdv().getParamDefaultsEditor().getParamRange(param.getName());
        if (range == null) {
            range = new Range(1.0, 100.0);
        }

        ColorTableWidget ctw = null;
        try {
            if (colorRangeChanged) {
                range = getStormTrackControl().getRangeForColorTable();
            }
            ctw = getStormTrackControl().getColorTableWidget(range);
        } catch (VisADException r) {
        } catch (RemoteException s) {
        }
        ColorTable ct = ctw.getColorTable();
        // getStormTrackControl().getIdv().getParamDefaultsEditor()
        // .getParamColorTable(param.getName(), false);
        if (ct == null) {
            ct = getStormTrackControl().getColorTable();
        }
        // ct.setRange(range);

        return ct;
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    protected StationModel getObsLayoutModel() {
        if ((obsLayoutModelName == null) || obsLayoutModelName.equals("none")) {
            return null;
        }

        StationModelManager smm = stormTrackControl.getControlContext().getStationModelManager();
        return smm.getStationModel(obsLayoutModelName);
        /*
         * StationModel model = new StationModel("TrackLocation"); ShapeSymbol
         * shapeSymbol = new ShapeSymbol(0, 0);
         * shapeSymbol.setShape(ucar.visad.ShapeUtility.HURRICANE);
         * shapeSymbol.setScale(2.0f); shapeSymbol.bounds = new
         * java.awt.Rectangle(-15, -15, 30, 30);
         * shapeSymbol.setRectPoint(Glyph.PT_MM);
         * shapeSymbol.setForeground(null); model.addSymbol(shapeSymbol); return
         * model;
         */
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    protected StationModel getObsPointLayoutModel() {
        if ((obsPointLayoutModelName == null) || obsPointLayoutModelName.equals("none")) {
            return null;
        }

        StationModelManager smm = stormTrackControl.getControlContext().getStationModelManager();
        return smm.getStationModel(obsPointLayoutModelName);
        /*
         * StationModel model = new StationModel("TrackLocation"); ShapeSymbol
         * shapeSymbol = new ShapeSymbol(0, 0);
         * shapeSymbol.setShape(ucar.visad.ShapeUtility.HURRICANE);
         * shapeSymbol.setScale(2.0f); shapeSymbol.bounds = new
         * java.awt.Rectangle(-15, -15, 30, 30);
         * shapeSymbol.setRectPoint(Glyph.PT_MM);
         * shapeSymbol.setForeground(null); model.addSymbol(shapeSymbol); return
         * model;
         */
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    protected StationModel getForecastLayoutModel() {
        if ((forecastLayoutModelName == null) || forecastLayoutModelName.equals("none")) {
            return null;
        }
        StationModelManager smm = stormTrackControl.getControlContext().getStationModelManager();
        StationModel sm = smm.getStationModel(forecastLayoutModelName);
        if (sm != null) {
            return sm;
        }
        StationModel model = new StationModel("TrackLocation");
        ShapeSymbol shapeSymbol = new ShapeSymbol(0, 0);
        shapeSymbol.setScale(0.3f);
        shapeSymbol.setShape(ucar.visad.ShapeUtility.CIRCLE);
        shapeSymbol.bounds = new java.awt.Rectangle(-15, -15, 30, 30);
        shapeSymbol.setRectPoint(Glyph.PT_MM);
        model.addSymbol(shapeSymbol);
        return model;
    }

    // ucar.visad.Util.makeTimeField(List<Data> ranges, List times)

    /**
     * Animation animation = stormTrackControl.getViewAnimation(); if (animation
     * == null) { return; } List<StormTrack> visibleTracks = new
     * ArrayList<StormTrack>(); Real currentAnimationTime =
     * animation.getAniValue(); if (currentAnimationTime == null ||
     * currentAnimationTime.isMissing()) { return; } Iterate way display states
     * boolean visible = false; if(wds.shouldShowTrack() &&
     * wds.hasTrackDisplay()) { FieldImpl field =
     * (FieldImplt)wds.getTrackDisplay().getData() if(field==null) continue; Set
     * timeSet = GridUtil.getTimeSet(); if(timeSet == null) continue; if
     * (timeSet.getLength() == 1) { visible = true; } else { //Else work the
     * visad magic float timeValueFloat = (float) currentAnimationTime.getValue(
     * timeSet.getSetUnits()[0]); // System.err.println("multiple times:" +
     * timeValueFloat); float[][] value = { { timeValueFloat } }; int[] index =
     * timeSet.valueToIndex(value); // System.err.println("index:" + index[0]);
     * visible = (index[0] >= 0); } if(visible) { //Find the closest track in
     * wds in time visibleTracks.add(..); } }
     * 
     * 
     * Now search in space
     * 
     * @param stormTrackChart
     *            _more_
     */

    /**
     * _more_
     * 
     * @param stormTrackChart
     *            _more_
     */
    protected void removeChart(StormTrackChart stormTrackChart) {
        charts.remove(stormTrackChart);
        tabbedPane.remove(stormTrackChart.getContents());
    }

    /**
     * _more_
     */
    public void addForecastTimeChart() {
        addForecastChart(StormTrackChart.MODE_FORECASTTIME);
    }

    /**
     * _more_
     */
    public void addForecastHourChart() {
        addForecastChart(StormTrackChart.MODE_FORECASTHOUR);
    }

    /**
     * _more_
     * 
     * @param mode
     *            _more_
     */
    public void addForecastChart(int mode) {
        String chartName = GuiUtils.getInput("Please enter a chart name", "Chart Name: ", "Storm Chart");
        if (chartName == null) {
            return;
        }
        StormTrackChart stormTrackChart = new StormTrackChart(this, chartName, mode);
        charts.add(stormTrackChart);
        tabbedPane.addTab(stormTrackChart.getName(), stormTrackChart.getContents());
        stormTrackChart.updateChart();

    }

    /**
     * _more_
     * 
     * @return _more_
     */
    public List<StormParam> getStormChartParams() {
        Hashtable<String, Boolean> s1 = stormTrackControl.getOkParams();
        List<StormParam> allParams = stormTrackControl.getTrackParams();
        List<StormParam> params = new ArrayList();
        for (StormParam sp : allParams) {
            Boolean v = s1.get(sp.getName());
            if ((v != null) && v.booleanValue()) {
                params.add(sp);
            }
        }
        return params;
        // return stormTrackControl.getChartParamFromSelector();
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    private JComponent getTrackTable() {
        final Font boldFont = new Font("Dialog", Font.BOLD, 10);
        final Font plainFont = new Font("Dialog", Font.PLAIN, 10);
        tableTreePanel = new TreePanel(true, 150) {
            public DefaultTreeCellRenderer doMakeTreeCellRenderer() {
                return new DefaultTreeCellRenderer() {
                    public Component getTreeCellRendererComponent(JTree theTree, Object value, boolean sel,
                            boolean expanded, boolean leaf, int row, boolean hasFocus) {
                        super.getTreeCellRendererComponent(theTree, value, sel, expanded, leaf, row, hasFocus);
                        if (!(value instanceof TreePanel.MyTreeNode)) {
                            return this;
                        }
                        TreePanel.MyTreeNode node = (TreePanel.MyTreeNode) value;
                        StormTrack track = (StormTrack) node.getObject();
                        if (track.getIsEdited()) {
                            this.setFont(boldFont);
                            this.setForeground(Color.red);
                        } else {
                            this.setFont(plainFont);
                            this.setForeground(Color.black);
                        }
                        return this;
                    }
                };
            }
        };

        int width = 400;
        int height = 400;
        for (StormTrack track : trackCollection.getTracks()) {
            final StormTrack theTrack = track;
            StormTrackTableModel tableModel = new StormTrackTableModel(this, track);
            tableModels.add(tableModel);
            TableSorter sorter = new TableSorter(tableModel);
            JTable trackTable = new JTable(sorter);
            JTableHeader header = trackTable.getTableHeader();
            header.setToolTipText("Click to sort");
            sorter.setTableHeader(trackTable.getTableHeader());

            JScrollPane scroller = GuiUtils.makeScrollPane(trackTable, width, height);
            scroller.setBorder(BorderFactory.createLoweredBevelBorder());
            JComponent contents = scroller;
            if (!track.getWay().isObservation()) {
                contents = GuiUtils.topCenter(
                        GuiUtils.left(GuiUtils.inset(new JLabel(track.getStartTime().toString()), 5)), contents);
            }

            track.putTemporaryProperty(PROP_TRACK_TABLE, contents);

            JButton flythroughBtn = GuiUtils.makeButton("Fly through", this, "flythroughTrack", track);
            contents = GuiUtils.centerBottom(contents, GuiUtils.right(flythroughBtn));
            tableTreePanel.addComponent(contents, track.getWay().toString(), track.getStartTime().toString(), null,
                    track);
        }

        return tableTreePanel;
    }

    /**
     * _more_
     * 
     * @param track
     *            _more_
     */
    public void flythroughTrack(StormTrack track) {
        try {
            List<FlythroughPoint> points = new ArrayList<FlythroughPoint>();
            for (StormTrackPoint stp : track.getTrackPoints()) {
                EarthLocation newLoc = makePoint(stp.getLocation().getLatitude().getValue(CommonUnit.degree),
                        stp.getLocation().getLongitude().getValue(CommonUnit.degree));
                points.add(new FlythroughPoint(newLoc, stp.getTime()));
            }
            stormTrackControl.getMapViewManager().flythrough(points);
        } catch (Exception exc) {
            stormTrackControl.logException("Doing flythrough", exc);
        }
    }

    /** _more_ */
    public static final PatternFileFilter FILTER_DAT = new PatternFileFilter(".+\\.dat", "Diamond Format (*.dat)",
            ".dat");

    /** _more_ */
    JCheckBox obsCbx = new JCheckBox("Observation", true);

    /** _more_ */
    JCheckBox forecastCbx = new JCheckBox("Forecast", true);

    /** _more_ */
    JCheckBox mostRecentCbx = new JCheckBox("Most Recent Forecasts", false);

    /** _more_ */
    JCheckBox editedCbx = new JCheckBox("Edited Tracks", false);

    /**
     * _more_
     */
    public void writeToDataFile() {
        try {
            JComponent accessory = GuiUtils.top(GuiUtils.vbox(obsCbx, forecastCbx, mostRecentCbx, editedCbx));

            String filename = FileManager.getWriteFile(Misc.newList(FileManager.FILTER_XLS, FILTER_DAT),
                    FileManager.SUFFIX_XLS, accessory);
            if (filename == null) {
                return;
            }

            List<StormTrack> tracksToWrite = new ArrayList<StormTrack>();
            List<Way> waysToUse = new ArrayList<Way>();
            Hashtable<Way, List> trackMap = new Hashtable<Way, List>();
            for (StormTrack track : trackCollection.getTracks()) {
                List tracks = trackMap.get(track.getWay());
                if (tracks == null) {
                    tracks = new ArrayList();
                    trackMap.put(track.getWay(), tracks);
                    waysToUse.add(track.getWay());
                }
                tracks.add(track);
                if (editedCbx.isSelected()) {
                    if (track.getIsEdited()) {
                        tracksToWrite.add(track);
                    }
                } else {
                    if (track.getWay().isObservation()) {
                        if (obsCbx.isSelected()) {
                            tracksToWrite.add(track);
                        }
                    } else {
                        if (forecastCbx.isSelected()) {
                            tracksToWrite.add(track);
                        }

                    }
                }

            }

            if (filename.endsWith(".dat")) {
                StringBuffer sb = StormTrack.toDiamond7(tracksToWrite, stormInfo.getStormId());
                IOUtil.writeFile(filename, sb.toString());
                return;
            }

            Hashtable sheetNames = new Hashtable();
            HSSFWorkbook wb = new HSSFWorkbook();
            StormTrack obsTrack = trackCollection.getObsTrack();
            // Write the obs track first
            if ((obsTrack != null) && obsCbx.isSelected()) {
                write(wb, obsTrack, sheetNames);
            }
            if (forecastCbx.isSelected()) {
                waysToUse = Misc.sort(waysToUse);
                for (Way way : waysToUse) {
                    if (way.isObservation()) {
                        continue;
                    }
                    List<StormTrack> tracks = (List<StormTrack>) Misc.sort(trackMap.get(way));
                    if (mostRecentCbx.isSelected()) {
                        write(wb, tracks.get(tracks.size() - 1), sheetNames);
                    } else {
                        for (StormTrack track : tracks) {
                            write(wb, track, sheetNames);
                        }
                    }
                }
            }
            FileOutputStream fileOut = new FileOutputStream(filename);
            wb.write(fileOut);
            fileOut.close();
        } catch (Exception exc) {
            stormTrackControl.logException("Writing spreadsheet", exc);
        }
    }

    /**
     * _more_
     * 
     * @param wb
     *            _more_
     * @param track
     *            _more_
     * @param sheetNames
     *            _more_
     */
    protected void write(HSSFWorkbook wb, StormTrack track, Hashtable sheetNames) {
        int cnt = 0;
        String dateString = track.getStartTime().formattedString("yyyy-MM-dd hhmm", DateUtil.TIMEZONE_GMT);
        String sheetName = track.getWay() + " - " + dateString;
        if (sheetName.length() > 30) {
            sheetName = sheetName.substring(0, 29);
        }
        // The sheet name length is limited
        while (sheetNames.get(sheetName) != null) {
            sheetName = (cnt++) + " " + sheetName;
            if (sheetName.length() > 30) {
                sheetName = sheetName.substring(0, 29);
            }
        }
        sheetNames.put(sheetName, sheetName);
        HSSFSheet sheet = wb.createSheet(sheetName);

        int rowCnt = 0;
        List<StormParam> params = track.getParams();
        HSSFCell cell;
        HSSFRow row;

        for (StormTrackPoint stp : track.getTrackPoints()) {
            if (rowCnt == 0) {
                row = sheet.createRow((short) rowCnt++);
                row.createCell(0).setCellValue(new HSSFRichTextString("Time"));
                row.createCell(1).setCellValue(new HSSFRichTextString("Latitude"));
                row.createCell(2).setCellValue(new HSSFRichTextString("Longitude"));
                for (int colIdx = 0; colIdx < params.size(); colIdx++) {
                    row.createCell((colIdx + 3))
                            .setCellValue(new HSSFRichTextString(params.get(colIdx).toString()));
                }
            }
            row = sheet.createRow((short) rowCnt++);
            row.createCell(0).setCellValue(new HSSFRichTextString(stp.getTime().toString()));
            row.createCell(1).setCellValue(stp.getLocation().getLatitude().getValue());
            row.createCell(2).setCellValue(stp.getLocation().getLongitude().getValue());
            for (int colIdx = 0; colIdx < params.size(); colIdx++) {
                Real r = stp.getAttribute(params.get(colIdx));
                cell = row.createCell((colIdx + 3));
                cell.setCellValue(r.getValue());
            }
        }
    }

    /**
     * _more_
     * 
     * @param docNode
     *            _more_
     * @param state
     *            _more_
     * @param doObs
     *            _more_
     * @param doForecast
     *            _more_
     * @param mostRecent
     *            _more_
     * 
     * @throws RemoteException
     *             _more_
     * @throws VisADException
     *             _more_
     */
    public void writeToKml(Element docNode, Hashtable state, boolean doObs, boolean doForecast, boolean mostRecent)
            throws VisADException, RemoteException {
        try {
            List<Way> waysToUse = new ArrayList<Way>();
            Hashtable<Way, List> trackMap = new Hashtable<Way, List>();
            for (StormTrack track : trackCollection.getTracks()) {
                List tracks = trackMap.get(track.getWay());
                if (tracks == null) {
                    tracks = new ArrayList();
                    trackMap.put(track.getWay(), tracks);
                    waysToUse.add(track.getWay());
                }
                tracks.add(track);
            }

            Element topFolder = KmlUtil.folder(docNode, "Storm: " + stormInfo.toString() + "   "
                    + stormInfo.getStartTime().formattedString("yyyy-MM-dd", DateUtil.TIMEZONE_GMT));
            StormTrack obsTrack = trackCollection.getObsTrack();
            // Write the obs track first
            if ((obsTrack != null) && doObs) {
                Element obsFolder = KmlUtil.folder(topFolder, "Observation");
                stormTrackControl.writeToGE(docNode, state, obsFolder, obsTrack,
                        getWayDisplayState(obsTrack.getWay()).getColor());
            }
            if (doForecast) {
                waysToUse = Misc.sort(waysToUse);
                for (Way way : waysToUse) {
                    if (way.isObservation()) {
                        continue;
                    }
                    Element wayNode = KmlUtil.folder(topFolder, stormTrackControl.getWayName() + ": " + way);
                    List<StormTrack> tracks = (List<StormTrack>) Misc.sort(trackMap.get(way));
                    if (mostRecent) {
                        StormTrack recent = tracks.get(tracks.size() - 1);
                        stormTrackControl.writeToGE(docNode, state, wayNode, recent,
                                getWayDisplayState(recent.getWay()).getColor());
                    } else {
                        for (StormTrack track : tracks) {
                            stormTrackControl.writeToGE(docNode, state, wayNode, track,
                                    getWayDisplayState(track.getWay()).getColor());

                        }
                    }
                }
            }

        } catch (Exception exc) {
            stormTrackControl.logException("Writing KML", exc);
        }
    }

    /**
     * Set the StormInfo property.
     * 
     * @param value
     *            The new value for StormInfo
     */
    public void setStormInfo(StormInfo value) {
        stormInfo = value;
    }

    /**
     * Get the StormInfo property.
     * 
     * @return The StormInfo
     */
    public StormInfo getStormInfo() {
        return stormInfo;
    }

    /**
     * Set the Changed property.
     * 
     * @param value
     *            The new value for Changed
     */
    public void setChanged(boolean value) {
        changed = value;
    }

    /**
     * Get the Changed property.
     * 
     * @return The Changed
     */
    public boolean getChanged() {
        return changed;
    }

    /**
     * Set the Active property.
     * 
     * @param value
     *            The new value for Active
     */
    public void setActive(boolean value) {
        active = value;
    }

    /**
     * Get the Active property.
     * 
     * @return The Active
     */
    public boolean getActive() {
        return active;
    }

    /**
     * Set the WayDisplayStateMap property.
     * 
     * @param value
     *            The new value for WayDisplayStateMap
     */
    public void setWayDisplayStateMap(Hashtable<Way, WayDisplayState> value) {
        wayDisplayStateMap = value;
    }

    /**
     * Get the WayDisplayStateMap property.
     * 
     * @return The WayDisplayStateMap
     */
    public Hashtable<Way, WayDisplayState> getWayDisplayStateMap() {
        return wayDisplayStateMap;
    }

    /**
     * Set the ForecastState property.
     * 
     * @param value
     *            The new value for ForecastState
     */
    public void setForecastState(WayDisplayState value) {
        forecastState = value;
    }

    /**
     * Get the ForecastState property.
     * 
     * @return The ForecastState
     */
    public WayDisplayState getObservationState() {
        return obsDisplayState;
    }

    /**
     * Set the ForecastState property.
     * 
     * @param value
     *            The new value for ForecastState
     */
    public void setObservationState(WayDisplayState value) {
        obsDisplayState = value;
    }

    /**
     * Get the ForecastState property.
     * 
     * @return The ForecastState
     */
    public WayDisplayState getForecastState() {
        return forecastState;
    }

    /**
     * Cycle through the color list.
     * 
     * 
     * @param nextColor
     *            _more_
     * @return The next color in the list
     */
    public static Color getNextColor(int[] nextColor) {
        if (nextColor[0] >= colors.length) {
            nextColor[0] = 0;
        }
        return colors[nextColor[0]++];
    }

    /**
     * Set the Charts property.
     * 
     * @param value
     *            The new value for Charts
     */
    public void setCharts(List<StormTrackChart> value) {
        charts = value;
    }

    /**
     * Get the Charts property.
     * 
     * @return The Charts
     */
    public List<StormTrackChart> getCharts() {
        return charts;
    }

    /**
     * Set the Params property.
     * 
     * @param value
     *            The new value for Params
     */
    public void setParams(Hashtable value) {
        params = value;
    }

    /**
     * Get the Params property.
     * 
     * @return The Params
     */
    public Hashtable getParams() {
        return params;
    }

    /**
     * Set the ObsLayoutModelName property.
     * 
     * @param value
     *            The new value for ObsLayoutModelName
     */
    public void setObsLayoutModelName(String value) {
        obsLayoutModelName = value;
    }

    /**
     * Get the ObsLayoutModelName property.
     * 
     * @return The ObsLayoutModelName
     */
    public String getObsLayoutModelName() {
        return obsLayoutModelName;
    }

    /**
     * Set the ObsLayoutModelName property.
     * 
     * @param value
     *            The new value for ObsLayoutModelName
     */
    public void setObsPointLayoutModelName(String value) {
        obsPointLayoutModelName = value;
    }

    /**
     * Get the ObsLayoutModelName property.
     * 
     * @return The ObsLayoutModelName
     */
    public String getObsPointLayoutModelName() {
        return obsPointLayoutModelName;
    }

    /**
     * Set the ForecastLayoutModelName property.
     * 
     * @param value
     *            The new value for ForecastLayoutModelName
     */
    public void setForecastLayoutModelName(String value) {
        forecastLayoutModelName = value;
    }

    /**
     * Get the ForecastLayoutModelName property.
     * 
     * @return The ForecastLayoutModelName
     */
    public String getForecastLayoutModelName() {
        return forecastLayoutModelName;
    }

    /**
     * _more_
     * 
     * @return _more_
     */
    public int getForecastAnimationMode() {
        return forecastAnimationMode;
    }

    /**
     * _more_
     * 
     * @param value
     *            _more_
     */
    public void setForecastAnimationMode(int value) {
        forecastAnimationMode = value;
    }

    /**
     * _more_
     */
    public void markHasBeenEdited() {
        hasBeenEdited = true;
    }

    public void colorRangeChanged() {
        DisplayMaster displayMaster = stormTrackControl.getDisplayMaster();
        colorRangeChanged = true;
        displayMaster.setDisplayInactive();
        try {
            stormTrackControl.stormChanged(StormDisplayState.this);
            updateDisplays();
        } catch (Exception exc) {
            stormTrackControl.logException("Changing color table", exc);
        }

    }

    /**
     * _more_
     */
    public boolean isColorRangeChanged() {
        return colorRangeChanged;
    }
}