package nxm.sys.libg;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.List;

import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import nxm.sys.lib.StringUtil;
import nxm.sys.lib.Table;

/** A graphical panel used to configure a particular {@link LayerWMS} layer. This is a separate
    class from {@link LayerWMS} so that the NetBeans GUI builder can be used.

    @author  J. Czechowski IV
    @since   NeXtMidas 2.3.1
    @see     LayerWMS
*/
@SuppressWarnings({"serial","rawtypes","unchecked"}) // <-- NOTE: JDK7 has some rawtypes & unchecked Xlint warnings that CANNOT be fixed since we still support JDK6
class LayerWmsConfig extends JPanel implements TreeSelectionListener, ListSelectionListener {

  private static final StyleInfo DEFAULT_STYLE = new StyleInfo(null);
  private static final Icon ENABLED  = new Symbol.CheckBox(true);
  private static final Icon DISABLED = new Symbol.CheckBox(false);

  private LayerWMS layerWms;
  private CellRendererTree rendererTree;
  private CellRendererList rendererList;
  private DefaultListModel listModel;
  private Object infoKey;
  private JComponent[] mapComponents;
  private DragListener dragListener;
  private String listDragStart = null;

  /** Creates new form LayerWmsConfig */
  public LayerWmsConfig (LayerWMS layerWms) {
    this.layerWms = layerWms;

    initComponents();

    // Initialize the mapList
    rendererList = new CellRendererList();
    dragListener = new DragListener();
    listModel    = new DefaultListModel();
    updateListModel();
    mapList.setCellRenderer(rendererList);
    mapList.setModel(listModel);
    mapList.addMouseListener(dragListener);
    mapList.addMouseListener(new ListMouseListener(mapList));
    mapList.addMouseMotionListener(dragListener);
    mapList.addListSelectionListener(this);


    // Initialize the mapTree
    TreeSelectionModel selModel = mapTree.getSelectionModel();
    rendererTree = new CellRendererTree();

    mapTree.addMouseListener(new TreeMouseListener(mapTree));
    mapTree.setModel(new DefaultTreeModel(buildTree(layerWms.getAllLayersTable())));
    mapTree.setCellRenderer(rendererTree);
    selModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
    selModel.addTreeSelectionListener(this);

    // Initialize mapComponents
    mapComponents = new JComponent[] { mapEnable, mapName, mapId, mapStyle, mapAbstract,
                                       mapCrs, mapMinLat, mapMaxLat, mapMinLon, mapMaxLon };
    infoKey = "";
    refreshConfig();
  }

  public void valueChanged (TreeSelectionEvent e) {
    Object key = e.getPath().getLastPathComponent();
    showInfoFor(key);
    mapList.clearSelection();
  }

  public void valueChanged (ListSelectionEvent e) {
    if (listDragStart == null) { // Ignore during a drag
      int index = mapList.getSelectedIndex();
      if (index >= 0) {
        String name  = listModel.getElementAt(index).toString();
        String key   = layerWms.findLayerKey(name);
        showInfoFor(key);
      } else {
        showInfoFor(null);
      }
    } else {
      String key = layerWms.findLayerKey(listDragStart);
      showInfoFor(key);
    }
    mapTree.clearSelection();
  }

  @SuppressWarnings("unchecked")
  private void showInfoFor (Object val) {
    if (!layerWms.isConnected()) { val = null; }

    infoKey = null;

    String  title     = "Layer Title";
    String  name      = null;
    String  id        = "0000";
    String  enableStr = "Enable";
    boolean enabled   = false;
    boolean enableAll = false;
    String  crs       = "none";
    String  srs       = "none";
    String  minLat    = "";
    String  maxLat    = "";
    String  minLon    = "";
    String  maxLon    = "";
    String  abstDescr = "";

    mapStyle.removeAllItems();
    if (val != null) {
      String key  = val.toString();
      Table  info = layerWms.getAvailableLayer(key);
      if (info != null) {
        title     = info.getS("TITLE");
        name      = info.getS("NAME");
        id        = info.getS("IDENTIFIER");
        crs       = info.getS(LayerWMS.CRS_KEY, srs);
        abstDescr = info.getS("ABSTRACT", "");
        enableStr = "Enable";
        enabled   = info.getState(LayerWMS.ENABLED_KEY);

        Table layers = info.getTable(LayerWMS.LAYERS_KEY);
        if ((layers != null) && (layers.size() > 0)) {
          enableStr = "Enable All";
        }

        String   selStyle = info.getS(LayerWMS.STYLE_KEY,"");
        Table    styles   = info.getTable(LayerWMS.STYLES_KEY);
        String[] keys     = (styles != null)? styles.getKeys(): new String[0];

        for (int i = 0; i <= keys.length; i++) {
          StyleInfo styleInfo = (i==0)? DEFAULT_STYLE: new StyleInfo(styles.getTable(keys[i-1]));
          mapStyle.addItem(styleInfo);
          if (styleInfo.equals(selStyle)) {
            mapStyle.setSelectedIndex(i);
          }
        }

        Table boundingBox = info.getTable("EX_GEOGRAPHICBOUNDINGBOX");
        if (boundingBox != null) {
          minLat = boundingBox.getS("SOUTHBOUNDLATITUDE","");
          maxLat = boundingBox.getS("NORTHBOUNDLATITUDE","");
          minLon = boundingBox.getS("WESTBOUNDLONGITUDE","");
          maxLon = boundingBox.getS("EASTBOUNDLONGITUDE","");
        }
      }
      enableAll = !key.equals("");
    }
    for (int i = 0; i < mapComponents.length; i++) {
      mapComponents[i].setEnabled(enableAll);
    }
    mapTitle.setText(title);
    mapId.setText(id);
    mapAbstract.setText(abstDescr);
    mapCrs.setText(crs);
    mapMinLat.setText(StringUtil.padLeft(minLat, 10));
    mapMaxLat.setText(StringUtil.padLeft(maxLat, 10));
    mapMinLon.setText(StringUtil.padLeft(minLon, 10));
    mapMaxLon.setText(StringUtil.padLeft(maxLon, 10));
    if (name == null) {
      if (val != null) { enableStr = "Can Not Enable"; }
      mapEnable.setText(enableStr);
      mapEnable.setSelected(false);
      mapEnable.setEnabled(false);
      mapName.setText("");
    } else {
      mapEnable.setText(enableStr);
      mapEnable.setSelected(enabled);
      mapName.setText(name);
    }
    infoKey = val;
  }

  private MutableTreeNode buildTree (Table tbl) {
    TreeNodeWithIcon node;

    if (layerWms.isConnected()) {
      node = new TreeNodeWithIcon("",null);
      buildTree(node, tbl, LayerWMS.LAYERS_KEY+".");
    } else {
      node = new TreeNodeWithIcon("<not connected>",null);
    }
    return node;
  }

  private void buildTree (DefaultMutableTreeNode node, Table tbl, String prefix) {
    Table layers = (tbl == null)? null: tbl.getTable(LayerWMS.LAYERS_KEY);

    if (layers != null) {
      String[] keys = layers.getKeys();
      for (int i = 0; i < keys.length; i++) {
        String name  = prefix + keys[i];
        Table  layer = layers.getTable(keys[i]);

        TreeNodeWithIcon subNode = new TreeNodeWithIcon(name,null);
        node.add(subNode);
        buildTree(subNode, layer, name+"."+LayerWMS.LAYERS_KEY+".");
      }
    }
  }

  private void updateListModel () {
    listModel.removeAllElements();

    if (layerWms.isConnected()) {
      List<Table> allLayers = layerWms.getAllLayersList();
      for (int i = allLayers.size()-1; i >= 0; i--) {
        Table  layer = allLayers.get(i);
        String name  = layer.getS("NAME", null);

        if (name != null) {
          listModel.addElement(new ListElementWithIcon(name,null));
        }
      }
    }
  }

  /** Refreshes the configuration display and the map. Called internally. */
  private void refreshDisplay () {
    layerWms.refresh(); // <-- Calls refreshConfig()
  }

  /** Refreshes the configuration display. Called by LayerWMS. */
  void refreshConfig () {
    String bgcolorStr = layerWms.getBgColor();
    mapTree.repaint();
    updateListModel();
    showInfoFor(infoKey);
    mapBrightness.setValue(layerWms.getBrightness());
    mapContrast.setValue(layerWms.getContrast());
    transparentButton.setSelected(layerWms.getTransparent());
    configuration.setText(layerWms.getConfig().toString());
    downloadThreads.setValue(layerWms.getDownloadThreads());
    bgcolor.setText(bgcolorStr);
    bgcolor.setBackground(MColor.getColor(bgcolorStr));
    bgcolor.setForeground(Color.white);
    debugButton.setSelected(layerWms.getDebug());
    statusBar.setText(layerWms.getStatus());
  }

  /** This method is called from within the constructor to
   * initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is
   * always regenerated by the Form Editor.
   */
  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
  private void initComponents() {

    doneButton = new javax.swing.JButton();
    tabbedPane = new javax.swing.JTabbedPane();
    optionsPane = new javax.swing.JPanel();
    jLabel9 = new javax.swing.JLabel();
    jLabel10 = new javax.swing.JLabel();
    mapBrightness = new javax.swing.JSlider();
    mapContrast = new javax.swing.JSlider();
    mapBrightnessReset = new javax.swing.JButton();
    mapContrastReset = new javax.swing.JButton();
    jLabel11 = new javax.swing.JLabel();
    jLabel12 = new javax.swing.JLabel();
    debugButton = new javax.swing.JCheckBox();
    bgcolor = new javax.swing.JButton();
    jLabel13 = new javax.swing.JLabel();
    transparentButton = new javax.swing.JCheckBox();
    threadsButton = new javax.swing.JLabel();
    downloadThreads = new javax.swing.JSlider();
    jLabel14 = new javax.swing.JLabel();
    jScrollPane4 = new javax.swing.JScrollPane();
    configuration = new javax.swing.JTextArea();
    mapsPane = new javax.swing.JPanel();
    mapOptions = new javax.swing.JPanel();
    mapTitle = new javax.swing.JLabel();
    mapEnable = new javax.swing.JCheckBox();
    mapStyle = new javax.swing.JComboBox();
    jLabel1 = new javax.swing.JLabel();
    jLabel2 = new javax.swing.JLabel();
    mapName = new javax.swing.JTextField();
    jLabel3 = new javax.swing.JLabel();
    mapId = new javax.swing.JTextField();
    jLabel4 = new javax.swing.JLabel();
    jLabel5 = new javax.swing.JLabel();
    mapMinLat = new javax.swing.JTextField();
    mapMinLon = new javax.swing.JTextField();
    mapMaxLat = new javax.swing.JTextField();
    mapMaxLon = new javax.swing.JTextField();
    jLabel8 = new javax.swing.JLabel();
    mapCrs = new javax.swing.JTextField();
    jLabel7 = new javax.swing.JLabel();
    jLabel6 = new javax.swing.JLabel();
    jScrollPane2 = new javax.swing.JScrollPane();
    mapAbstract = new javax.swing.JTextArea();
    jTabbedPane1 = new javax.swing.JTabbedPane();
    jScrollPane1 = new javax.swing.JScrollPane();
    mapTree = new javax.swing.JTree();
    jScrollPane3 = new javax.swing.JScrollPane();
    mapList = new javax.swing.JList();
    layerName = new javax.swing.JLabel();
    statusBar = new javax.swing.JTextField();

    doneButton.setText("Done");
    doneButton.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        doneButtonActionPerformed(evt);
      }
    });

    jLabel9.setText("Brightness:");

    jLabel10.setText("Contrast:");

    mapBrightness.setMajorTickSpacing(25);
    mapBrightness.setMinorTickSpacing(1);
    mapBrightness.setPaintLabels(true);
    mapBrightness.setPaintTicks(true);
    mapBrightness.setSnapToTicks(true);
    mapBrightness.addChangeListener(new javax.swing.event.ChangeListener() {
      public void stateChanged(javax.swing.event.ChangeEvent evt) {
        mapBrightnessStateChanged(evt);
      }
    });

    mapContrast.setMajorTickSpacing(25);
    mapContrast.setMinorTickSpacing(1);
    mapContrast.setPaintLabels(true);
    mapContrast.setPaintTicks(true);
    mapContrast.setSnapToTicks(true);
    mapContrast.addChangeListener(new javax.swing.event.ChangeListener() {
      public void stateChanged(javax.swing.event.ChangeEvent evt) {
        mapContrastStateChanged(evt);
      }
    });

    mapBrightnessReset.setText("Reset");
    mapBrightnessReset.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        mapBrightnessResetActionPerformed(evt);
      }
    });

    mapContrastReset.setText("Reset");
    mapContrastReset.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        mapContrastResetActionPerformed(evt);
      }
    });

    jLabel11.setText("BgColor:");

    jLabel12.setText("Debug:");

    debugButton.setText("Output debug info to terminal.");
    debugButton.setToolTipText("Turns on/off debugging output. This is useful when debugging server/network problems.");
    debugButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
    debugButton.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        debugButtonActionPerformed(evt);
      }
    });

    bgcolor.setText("color");
    bgcolor.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        bgcolorActionPerformed(evt);
      }
    });

    jLabel13.setText("Transparent:");

    transparentButton.setText("Transparent background.");
    transparentButton.setToolTipText("Turns on/off transparent background.");
    transparentButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
    transparentButton.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        transparentButtonActionPerformed(evt);
      }
    });

    threadsButton.setText("Download Threads:");

    downloadThreads.setMajorTickSpacing(5);
    downloadThreads.setMaximum(40);
    downloadThreads.setPaintLabels(true);
    downloadThreads.setPaintTicks(true);
    downloadThreads.setSnapToTicks(true);
    downloadThreads.setToolTipText("of image download threads.");
    downloadThreads.setValue(2);
    downloadThreads.addChangeListener(new javax.swing.event.ChangeListener() {
      public void stateChanged(javax.swing.event.ChangeEvent evt) {
        downloadThreadsStateChanged(evt);
      }
    });

    jLabel14.setText("Configuration:");

    configuration.setColumns(20);
    configuration.setEditable(false);
    configuration.setLineWrap(true);
    configuration.setRows(5);
    jScrollPane4.setViewportView(configuration);

    javax.swing.GroupLayout optionsPaneLayout = new javax.swing.GroupLayout(optionsPane);
    optionsPane.setLayout(optionsPaneLayout);
    optionsPaneLayout.setHorizontalGroup(
      optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(optionsPaneLayout.createSequentialGroup()
        .addContainerGap()
        .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(optionsPaneLayout.createSequentialGroup()
            .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
              .addComponent(jLabel9, javax.swing.GroupLayout.DEFAULT_SIZE, 121, Short.MAX_VALUE)
              .addComponent(jLabel10, javax.swing.GroupLayout.DEFAULT_SIZE, 121, Short.MAX_VALUE)
              .addComponent(threadsButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
              .addComponent(downloadThreads, 0, 0, Short.MAX_VALUE)
              .addComponent(mapContrast, javax.swing.GroupLayout.DEFAULT_SIZE, 332, Short.MAX_VALUE)
              .addComponent(mapBrightness, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 332, Short.MAX_VALUE))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
              .addComponent(mapBrightnessReset)
              .addComponent(mapContrastReset)))
          .addGroup(optionsPaneLayout.createSequentialGroup()
            .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addGroup(optionsPaneLayout.createSequentialGroup()
                .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                  .addComponent(jLabel11, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 121, Short.MAX_VALUE)
                  .addComponent(jLabel13, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 121, Short.MAX_VALUE)
                  .addComponent(jLabel12, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 121, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                  .addComponent(transparentButton)
                  .addComponent(bgcolor)
                  .addComponent(debugButton)))
              .addGroup(optionsPaneLayout.createSequentialGroup()
                .addComponent(jLabel14, javax.swing.GroupLayout.DEFAULT_SIZE, 124, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane4, javax.swing.GroupLayout.PREFERRED_SIZE, 344, javax.swing.GroupLayout.PREFERRED_SIZE)))
            .addContainerGap(39, javax.swing.GroupLayout.PREFERRED_SIZE))))
    );
    optionsPaneLayout.setVerticalGroup(
      optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(optionsPaneLayout.createSequentialGroup()
        .addContainerGap()
        .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addComponent(jLabel9)
          .addComponent(mapBrightnessReset)
          .addComponent(mapBrightness, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(optionsPaneLayout.createSequentialGroup()
            .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addComponent(jLabel10)
              .addComponent(mapContrast, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addComponent(threadsButton)
              .addComponent(downloadThreads, javax.swing.GroupLayout.PREFERRED_SIZE, 57, javax.swing.GroupLayout.PREFERRED_SIZE)))
          .addComponent(mapContrastReset))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(optionsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(optionsPaneLayout.createSequentialGroup()
            .addComponent(jLabel11)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel13)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel12)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel14))
          .addGroup(optionsPaneLayout.createSequentialGroup()
            .addComponent(bgcolor)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(transparentButton)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(debugButton)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jScrollPane4, javax.swing.GroupLayout.PREFERRED_SIZE, 24, Short.MAX_VALUE)))
        .addContainerGap())
    );

    tabbedPane.addTab("Options", optionsPane);

    mapTitle.setText("Map Title");

    mapEnable.setText("Enabled");
    mapEnable.setToolTipText("Check here to enable/disable this layer.");
    mapEnable.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
    mapEnable.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        mapEnableActionPerformed(evt);
      }
    });

    mapStyle.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
    mapStyle.setToolTipText("Select the style for this layer.");
    mapStyle.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        mapStyleActionPerformed(evt);
      }
    });

    jLabel1.setText("Style:");

    jLabel2.setText("Name:");

    mapName.setEditable(false);
    mapName.setToolTipText("The \"readable\" name of the layer.");

    jLabel3.setText("ID:");

    mapId.setEditable(false);
    mapId.setToolTipText("The server's name for this layer.");

    jLabel4.setText("Abstract:");

    jLabel5.setText("Bounding Box:");

    mapMinLat.setEditable(false);
    mapMinLat.setFont(new java.awt.Font("Monospaced", 0, 12));
    mapMinLat.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
    mapMinLat.setText("dd'mm'ss.sssN");
    mapMinLat.setToolTipText("Minimum Latitude");

    mapMinLon.setEditable(false);
    mapMinLon.setFont(new java.awt.Font("Monospaced", 0, 12));
    mapMinLon.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
    mapMinLon.setText("ddd'mm'ss.sssE");
    mapMinLon.setToolTipText("Minimum Longitude");

    mapMaxLat.setEditable(false);
    mapMaxLat.setFont(new java.awt.Font("Monospaced", 0, 12));
    mapMaxLat.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
    mapMaxLat.setText("dd'mm'ss.sssN");
    mapMaxLat.setToolTipText("Maximum Latitude");

    mapMaxLon.setEditable(false);
    mapMaxLon.setFont(new java.awt.Font("Monospaced", 0, 12));
    mapMaxLon.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
    mapMaxLon.setText("ddd'mm'ss.sssE");
    mapMaxLon.setToolTipText("Maximum Longitude");

    jLabel8.setText("CRS:");

    mapCrs.setEditable(false);
    mapCrs.setToolTipText("List of available geographic Coordinate Reference Systems (CRS) available.");

    jLabel7.setText("Lon:");

    jLabel6.setText("Lat:");

    mapAbstract.setEditable(false);
    mapAbstract.setLineWrap(true);
    mapAbstract.setTabSize(2);
    mapAbstract.setToolTipText("A description of the layer, if available from the server.");
    mapAbstract.setWrapStyleWord(true);
    mapAbstract.setOpaque(false);
    jScrollPane2.setViewportView(mapAbstract);

    javax.swing.GroupLayout mapOptionsLayout = new javax.swing.GroupLayout(mapOptions);
    mapOptions.setLayout(mapOptionsLayout);
    mapOptionsLayout.setHorizontalGroup(
      mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(mapOptionsLayout.createSequentialGroup()
        .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mapOptionsLayout.createSequentialGroup()
            .addContainerGap()
            .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mapOptionsLayout.createSequentialGroup()
                .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                  .addComponent(jLabel4, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 160, Short.MAX_VALUE)
                  .addComponent(jLabel5, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 160, Short.MAX_VALUE))
                .addGap(107, 107, 107))
              .addGroup(mapOptionsLayout.createSequentialGroup()
                .addGap(6, 6, 6)
                .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                  .addComponent(jLabel7)
                  .addComponent(jLabel6))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                  .addComponent(mapMinLat)
                  .addComponent(mapMinLon))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                  .addComponent(mapMaxLat)
                  .addComponent(mapMaxLon)))
              .addGroup(mapOptionsLayout.createSequentialGroup()
                .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                  .addComponent(jLabel2, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                  .addComponent(jLabel3, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                  .addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                  .addComponent(jLabel8, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                  .addComponent(mapName, javax.swing.GroupLayout.DEFAULT_SIZE, 202, Short.MAX_VALUE)
                  .addComponent(mapId, javax.swing.GroupLayout.DEFAULT_SIZE, 202, Short.MAX_VALUE)
                  .addComponent(mapStyle, 0, 202, Short.MAX_VALUE)
                  .addComponent(mapCrs, javax.swing.GroupLayout.DEFAULT_SIZE, 202, Short.MAX_VALUE)))))
          .addGroup(mapOptionsLayout.createSequentialGroup()
            .addContainerGap()
            .addComponent(mapEnable, javax.swing.GroupLayout.DEFAULT_SIZE, 267, Short.MAX_VALUE))
          .addComponent(mapTitle, javax.swing.GroupLayout.DEFAULT_SIZE, 279, Short.MAX_VALUE)
          .addGroup(mapOptionsLayout.createSequentialGroup()
            .addContainerGap()
            .addComponent(jScrollPane2)))
        .addContainerGap())
    );
    mapOptionsLayout.setVerticalGroup(
      mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(mapOptionsLayout.createSequentialGroup()
        .addComponent(mapTitle)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(mapEnable)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel2)
          .addComponent(mapName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel3)
          .addComponent(mapId, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel1)
          .addComponent(mapStyle, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel8)
          .addComponent(mapCrs, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jLabel5)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel6)
          .addComponent(mapMinLat, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(mapMaxLat, javax.swing.GroupLayout.PREFERRED_SIZE, 19, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(mapOptionsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel7)
          .addComponent(mapMinLon, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(mapMaxLon, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jLabel4)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 26, Short.MAX_VALUE)
        .addContainerGap())
    );

    jTabbedPane1.setTabPlacement(javax.swing.JTabbedPane.BOTTOM);
    jTabbedPane1.setToolTipText("Show all maps in a tree view.");

    mapTree.setToolTipText("Available layers as categorized by the server.");
    jScrollPane1.setViewportView(mapTree);

    jTabbedPane1.addTab("Tree View", jScrollPane1);

    mapList.setModel(new javax.swing.AbstractListModel() {
      String[] strings = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
      public int getSize() { return strings.length; }
      public Object getElementAt(int i) { return strings[i]; }
    });
    mapList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
    mapList.setToolTipText("Stacking order of the layers, layers on the top are drawn on top of layers on the bottom.");
    jScrollPane3.setViewportView(mapList);

    jTabbedPane1.addTab("Priority View", jScrollPane3);

    javax.swing.GroupLayout mapsPaneLayout = new javax.swing.GroupLayout(mapsPane);
    mapsPane.setLayout(mapsPaneLayout);
    mapsPaneLayout.setHorizontalGroup(
      mapsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mapsPaneLayout.createSequentialGroup()
        .addContainerGap()
        .addComponent(jTabbedPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 222, Short.MAX_VALUE)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(mapOptions, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
    );
    mapsPaneLayout.setVerticalGroup(
      mapsPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addComponent(mapOptions, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
      .addGroup(mapsPaneLayout.createSequentialGroup()
        .addComponent(jTabbedPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 313, Short.MAX_VALUE)
        .addContainerGap())
    );

    tabbedPane.addTab("Maps", mapsPane);

    layerName.setText("LayerWMS Configuration");

    statusBar.setEditable(false);
    statusBar.setToolTipText("Current application status.");

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
    this.setLayout(layout);
    layout.setHorizontalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addComponent(statusBar, javax.swing.GroupLayout.DEFAULT_SIZE, 539, Short.MAX_VALUE)
          .addComponent(layerName)
          .addComponent(doneButton, javax.swing.GroupLayout.Alignment.TRAILING)
          .addComponent(tabbedPane, javax.swing.GroupLayout.DEFAULT_SIZE, 539, Short.MAX_VALUE))
        .addContainerGap())
    );
    layout.setVerticalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(layerName)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(statusBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(tabbedPane, javax.swing.GroupLayout.DEFAULT_SIZE, 362, Short.MAX_VALUE)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(doneButton)
        .addContainerGap())
    );
  }// </editor-fold>//GEN-END:initComponents

private void downloadThreadsStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_downloadThreadsStateChanged
    layerWms.setDownloadThreads(downloadThreads.getValue());
    refreshDisplay();
}//GEN-LAST:event_downloadThreadsStateChanged

private void transparentButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_transparentButtonActionPerformed
    layerWms.setTransparent(transparentButton.isSelected());
    refreshDisplay();
}//GEN-LAST:event_transparentButtonActionPerformed

  private void debugButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_debugButtonActionPerformed
    layerWms.setDebug(debugButton.isSelected());
    refreshDisplay();
  }//GEN-LAST:event_debugButtonActionPerformed

  private void bgcolorActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bgcolorActionPerformed
// JC4: This method requires NeXtMidas 2.5.0
// System.err.println("LayerWMS: Graphical setting of BgColor requires NeXtMidas 2.5.0 or later.");
    Color color = MJPopup.showColor(this,"Background Color",JOptionPane.PLAIN_MESSAGE,"Select background color",layerWms.getBgColor());
    if (color != null) { layerWms.setBgColor(color); }
  }//GEN-LAST:event_bgcolorActionPerformed

  private void mapContrastStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_mapContrastStateChanged
    layerWms.setContrast(mapContrast.getValue());
  }//GEN-LAST:event_mapContrastStateChanged

  private void mapBrightnessStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_mapBrightnessStateChanged
    layerWms.setBrightness(mapBrightness.getValue());
  }//GEN-LAST:event_mapBrightnessStateChanged

  private void mapContrastResetActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mapContrastResetActionPerformed
    layerWms.setContrast(50);
  }//GEN-LAST:event_mapContrastResetActionPerformed

  private void mapBrightnessResetActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mapBrightnessResetActionPerformed
    layerWms.setBrightness(50);
  }//GEN-LAST:event_mapBrightnessResetActionPerformed

  private void mapStyleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mapStyleActionPerformed
    if (infoKey != null) {
      StyleInfo sty = (StyleInfo)mapStyle.getSelectedItem();
      Table info = layerWms.getAvailableLayer(infoKey.toString());
      info.put(LayerWMS.STYLE_KEY,sty.getName());
      refreshDisplay();
    }
  }//GEN-LAST:event_mapStyleActionPerformed

  private void mapEnableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mapEnableActionPerformed
    if (infoKey != null) {
      Table info = layerWms.getAvailableLayer(infoKey.toString());
      info.put(LayerWMS.ENABLED_KEY, mapEnable.isSelected());
      refreshDisplay();
    }
  }//GEN-LAST:event_mapEnableActionPerformed

  private void doneButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_doneButtonActionPerformed
    layerWms.setConfigPanelVisible(false);
  }//GEN-LAST:event_doneButtonActionPerformed

private void greyscaleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_greyscaleButtonActionPerformed
  boolean gs = layerWms.getGreyscale();
  layerWms.setGreyscale(!gs);
}//GEN-LAST:event_greyscaleButtonActionPerformed


  // Variables declaration - do not modify//GEN-BEGIN:variables
  private javax.swing.JButton bgcolor;
  private javax.swing.JTextArea configuration;
  private javax.swing.JCheckBox debugButton;
  private javax.swing.JButton doneButton;
  private javax.swing.JSlider downloadThreads;
  private javax.swing.JLabel jLabel1;
  private javax.swing.JLabel jLabel10;
  private javax.swing.JLabel jLabel11;
  private javax.swing.JLabel jLabel12;
  private javax.swing.JLabel jLabel13;
  private javax.swing.JLabel jLabel14;
  private javax.swing.JLabel jLabel2;
  private javax.swing.JLabel jLabel3;
  private javax.swing.JLabel jLabel4;
  private javax.swing.JLabel jLabel5;
  private javax.swing.JLabel jLabel6;
  private javax.swing.JLabel jLabel7;
  private javax.swing.JLabel jLabel8;
  private javax.swing.JLabel jLabel9;
  private javax.swing.JScrollPane jScrollPane1;
  private javax.swing.JScrollPane jScrollPane2;
  private javax.swing.JScrollPane jScrollPane3;
  private javax.swing.JScrollPane jScrollPane4;
  private javax.swing.JTabbedPane jTabbedPane1;
  private javax.swing.JLabel layerName;
  private javax.swing.JTextArea mapAbstract;
  private javax.swing.JSlider mapBrightness;
  private javax.swing.JButton mapBrightnessReset;
  private javax.swing.JSlider mapContrast;
  private javax.swing.JButton mapContrastReset;
  private javax.swing.JTextField mapCrs;
  private javax.swing.JCheckBox mapEnable;
  private javax.swing.JTextField mapId;
  private javax.swing.JList mapList;
  private javax.swing.JTextField mapMaxLat;
  private javax.swing.JTextField mapMaxLon;
  private javax.swing.JTextField mapMinLat;
  private javax.swing.JTextField mapMinLon;
  private javax.swing.JTextField mapName;
  private javax.swing.JPanel mapOptions;
  private javax.swing.JComboBox mapStyle;
  private javax.swing.JLabel mapTitle;
  private javax.swing.JTree mapTree;
  private javax.swing.JPanel mapsPane;
  private javax.swing.JPanel optionsPane;
  private javax.swing.JTextField statusBar;
  private javax.swing.JTabbedPane tabbedPane;
  private javax.swing.JLabel threadsButton;
  private javax.swing.JCheckBox transparentButton;
  // End of variables declaration//GEN-END:variables

  /** Used by the renderers. */
  private Icon getCheckbox(String key, boolean enabled) {
    Icon icon = (enabled)? ENABLED: DISABLED;

    return (key.equals(""))? null: icon;
  }

  /** Information about a style. */
  private static class StyleInfo {
    private Table style;

    private StyleInfo(Table style) {
      this.style = style;
    }

    public String toString() {
      return (style==null)? "<Default>": style.getS("TITLE",getName());
    }

    public String getName() {
      return (style==null)? "": style.getS("NAME");
    }

    public boolean equals(Object obj) {
      String thisName  = getName();
      String otherName;

      if (obj == null) {
        otherName = "";
      } else if (obj instanceof StyleInfo) {
        otherName = ((StyleInfo)obj).getName();
      } else {
        otherName = obj.toString();
      }
      return thisName.equals(otherName);
    }

    public int hashCode() {
      return getName().hashCode();
    }
  }

  /** A custom renderer for the table cells. */
  private class CellRendererTree extends DefaultTreeCellRenderer {

    public CellRendererTree() {
      this.setLeafIcon(null);
      this.setOpenIcon(null);
      this.setClosedIcon(null);
    }

    public Component getTreeCellRendererComponent(JTree tree, Object val, boolean sel, boolean exp,
                                                  boolean leaf, int row, boolean focus) {
      Component comp = super.getTreeCellRendererComponent(tree,val,sel,exp,leaf,row,focus);

      if (comp instanceof CellRendererTree) {
        CellRendererTree cr = (CellRendererTree)comp;

        String key  = val.toString();
        Table  info = layerWms.getAvailableLayer(key);

        if (info != null) {
          String  title   = info.getS("TITLE");
          boolean enabled = info.getState(LayerWMS.ENABLED_KEY);
          Icon    icon    = getCheckbox(key,enabled);

          if (val instanceof TreeNodeWithIcon) {
            TreeNodeWithIcon tnwi = (TreeNodeWithIcon)val;
            tnwi.setIcon(icon);
            icon = tnwi;
          }

          cr.setIcon(icon);
          cr.setText(title);
        }
      }
      return comp;
    }
  }

  /** A custom renderer for the table cells. */
  private class CellRendererList extends DefaultListCellRenderer {

    private final Border normalBorder;
    private final Border insertBorder;

    public CellRendererList() {
      this.setIcon(null);
      normalBorder = getBorder();
      insertBorder = new LineBorder(getForeground(), 2, true);
    }

    public Component getListCellRendererComponent(JList list, Object val, int index,
                                                  boolean sel, boolean focus) {
      Border  border;
      String  name;
      boolean enabled = true;

      if (val == null) {
        border = insertBorder;
        name   = listDragStart;
      } else {
        border = normalBorder;
        name   = val.toString();
      }

      Table   info   = layerWms.findAvailableLayer(name);
      boolean enable = (info==null)? false: info.getState(LayerWMS.ENABLED_KEY);
      String  title  = (info==null)? null:  info.getS("TITLE");
      Icon    icon   = (val==null)?  null:  getCheckbox(name, enable);

      if (listDragStart != null) {
        sel     = false;
        focus   = false;
        enabled = (!listDragStart.equals(name) && (val != null));
      }

      if (val instanceof ListElementWithIcon) {
        ListElementWithIcon lewi = (ListElementWithIcon)val;
        lewi.setIcon(icon);
        icon = lewi;
      }

      Component comp = super.getListCellRendererComponent(list,title,index,sel,focus);
      if (comp instanceof CellRendererList) {
        CellRendererList cr = (CellRendererList)comp;

        cr.setBorder(border);
        cr.setEnabled(enabled);
        cr.setIcon(icon);
        cr.setDisabledIcon(icon);
      }
      return comp;
    }
  }

  /** Drag listener for reordering mapList. */
  private class DragListener extends MouseAdapter implements MouseMotionListener {
    private int last = -1;

    public void mouseReleased(MouseEvent m) {
      if (listDragStart != null) {
        listModel.removeElement(null);
        int index = listModel.size() - last + 1;
        if (layerWms.moveAvailableLayer(listDragStart,index)) {
          mapTree.clearSelection();
        }
        listDragStart = null;
        last = -1;
      }
    }

    public void mouseDragged(MouseEvent m) {
      last = mapList.getSelectedIndex();

      if (last >= 0) {
        if (listDragStart != null) {
          listModel.removeElement(null);
          listModel.insertElementAt(null, last);
          showInfoFor(null);
        } else {
          listDragStart = listModel.getElementAt(last).toString();
        }
      }
    }

    public void mouseMoved(MouseEvent e) { }
  }

  /** A tree value with associated icon. */
  private static interface SaveSizeIcon extends Icon {
    /** The last bounds used by the icon. */
    Rectangle getIconBounds ();
  }

  /** A tree value with associated icon. */
  private static class TreeNodeWithIcon extends DefaultMutableTreeNode implements SaveSizeIcon {

    private final Rectangle bounds = new Rectangle(); // The last icon bounds
    private       Icon      icon   = null;            // This icon used

    /** Creates a new instance.
        @param userObject The value to use.
        @param icon       The icon to use.
     */
    public TreeNodeWithIcon (Object userObject, Icon icon) {
      super(userObject);
      this.icon  = icon;
    }

    /** Sets the icon to use. */
    public void setIcon (Icon icon) { this.icon = icon; }

    /** Gets the icon to use. */
    public Icon getIcon () { return icon; }

    public Rectangle getIconBounds () { return bounds; }
    public int       getIconWidth  () { return (icon == null) ? 0 : icon.getIconWidth();  }
    public int       getIconHeight () { return (icon == null) ? 0 : icon.getIconHeight(); }

    public void paintIcon (Component c, Graphics g, int x, int y) {
      if (icon != null) icon.paintIcon(c,g,x,y);
      bounds.setRect(x+c.getX(), y+c.getY(), getIconWidth(), getIconHeight());
    }
  }

  /** A tree value with associated icon. */
  private static class ListElementWithIcon implements SaveSizeIcon {
    private final Rectangle bounds  = new Rectangle(); // The last icon bounds
    private       Icon      icon    = null;            // This icon used
    private       Object    userObj = null;            // The user object.

    /** Creates a new instance.
        @param userObject The value to use.
        @param icon       The icon to use.
     */
    public ListElementWithIcon (Object userObject, Icon icon) {
      super();
      this.icon    = icon;
      this.userObj = userObject;
    }

    /** Sets the user object. */
    public void setUserObject (Object userObject) { this.userObj = userObject; }

    /** Gets the user object. */
    public Object getUserObject () { return userObj; }

    public String toString () { return (userObj == null)? null : userObj.toString(); }

    /** Sets the icon to use. */
    public void setIcon (Icon icon) { this.icon = icon; }

    /** Gets the icon to use. */
    public Icon getIcon () { return icon; }

    public Rectangle getIconBounds () { return bounds; }
    public int       getIconWidth  () { return (icon == null) ? 0 : icon.getIconWidth();  }
    public int       getIconHeight () { return (icon == null) ? 0 : icon.getIconHeight(); }

    public void paintIcon (Component c, Graphics g, int x, int y) {
      if (icon != null) icon.paintIcon(c,g,x,y);
      bounds.setRect(x+c.getX(), y+c.getY(), getIconWidth(), getIconHeight());
    }
  }

  /** Special mouse listener for a table that handles selections. <br>
      <br>
      Half of the code in here is taken from the example in the Java 5 docs for JTree, the other
      half is based on examples found on the internet.
   */
  private abstract class IconSelectionListener extends MouseAdapter {
    /** Maps mouse event to row.
        @return row or -1 if out of range
     */
    protected abstract int getRow (MouseEvent e);

    /** Gets WithIcon object for given row.
        @return Object or null if not found or not instance of WithIcon.
     */
    protected abstract SaveSizeIcon getIcon (MouseEvent e, int row);

    public void mousePressed (MouseEvent e) {
      int button = e.getButton();
      int selRow = getRow(e);

      if ((selRow != -1) && (button == MouseEvent.BUTTON1) && (e.getClickCount() == 1)) {
        SaveSizeIcon icon = getIcon(e,selRow);

        if (icon != null) {
          if (icon.getIconBounds().contains(e.getX(), e.getY())) {
            iconClicked(icon);
          }
        }
      }
    }

    /** Method called when the node is selected.
        @param icon The selected icon.
     */
    protected abstract void iconClicked (SaveSizeIcon icon);
  }

  /** Special {@link IconSelectionListener} for use with a {@link JTree}. */
  private class TreeMouseListener extends IconSelectionListener { // Change this method so class can be static.
    private final JTree tree; // The tree being used.

    public TreeMouseListener (JTree tree) {
      this.tree = tree;
    }

    protected int getRow (MouseEvent e) {
      return tree.getRowForLocation(e.getX(), e.getY());
    }

    protected SaveSizeIcon getIcon (MouseEvent e, int row) {
      TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
      Object   val     = selPath.getLastPathComponent();

      return (val instanceof SaveSizeIcon)? (SaveSizeIcon)val : null;
    }

    protected void iconClicked (SaveSizeIcon icon) {
//System.out.println("JTree -> infoKey = "+infoKey);
      if (infoKey != null) {
        Table info = layerWms.getAvailableLayer(infoKey.toString());
        boolean selected = info.getState(LayerWMS.ENABLED_KEY);
        info.put(LayerWMS.ENABLED_KEY, !selected);
        refreshDisplay();
      }
    }
  }

  /** Special {@link IconSelectionListener} for use with a {@link JList}. */
  private class ListMouseListener extends IconSelectionListener {
    private final JList list; // The tree being used.

    public ListMouseListener (JList list) {
      this.list = list;
    }

    protected int getRow (MouseEvent e) {
      return list.locationToIndex(e.getPoint());
    }

    protected SaveSizeIcon getIcon (MouseEvent e, int row) {
      Object val = list.getModel().getElementAt(row);

      return (val instanceof SaveSizeIcon)? (SaveSizeIcon)val : null;
    }

    protected void iconClicked (SaveSizeIcon icon) { // Change this method so class can be static.
//System.out.println("JList -> infoKey = "+infoKey);
      if (infoKey != null) {
        Table info = layerWms.getAvailableLayer(infoKey.toString());
        boolean selected = info.getState(LayerWMS.ENABLED_KEY);
        info.put(LayerWMS.ENABLED_KEY, !selected);
        refreshDisplay();
      }
    }
  }
}
