//
// MappingDialog.java
//

/*
VisAD system for interactive analysis and visualization of numerical
data.  Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library 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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/

package visad.ss;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.Hashtable;
import java.util.Vector;
import javax.swing.*;
import javax.swing.event.*;
import visad.*;
import visad.util.*;

/**
 * MappingDialog is a dialog that lets the user create ScalarMaps.
 */
public class MappingDialog extends JDialog
  implements ActionListener, ListSelectionListener, MouseListener
{
  /**
   * Flag whether user hit Done or Cancel button.
   */
  private boolean Confirm = false;

  /**
   * ScalarMaps selected by the user.
   */
  private ScalarMap[] ScalarMaps;


  // -- GUI components --

  private JComponent MathCanvas;
  private JScrollPane MathCanvasView;
  private JComponent CoordCanvas;
  private JScrollPane CoordCanvasView;
  private boolean CoordRefs;
  private JList MathList;
  private JComponent DisplayCanvas;
  private DefaultListModel CurMaps;
  private JList CurrentMaps;
  private JScrollPane CurrentMapsView;
  private JLabel description;


  // -- State info --

  /**
   * Array of ScalarTypes.
   */
  private ScalarType[] MathTypes;

  /**
   * Array of ScalarType names.
   */
  private String[] Scalars;

  /**
   * Array of ScalarType widths.
   */
  private int[] ScW;

  /**
   * Vector of MDTuples indicating ScalarType locations.
   */
  private Vector[] ScP;

  /**
   * ScalarType height.
   */
  private int ScH;

  /**
   * Width and height of MathType string.
   */
  private Dimension StrSize;

  /**
   * Width and height of CoordinateSystem string.
   */
  private Dimension CoordSize;

  /**
   * Flags marking whether each possible ScalarMap has been assigned.
   */
  private boolean[][][] Maps;

  /**
   * String representation of each possible ScalarMap.
   */
  private String[][][] CurMapLabel;

  /**
   * 11 pt monospaced font.
   */
  private static final Font Mono = new Font("Monospaced", Font.PLAIN, 11);

  /**
   * Names of system intrinsic DisplayRealTypes.
   */
  private static final String[][] MapNames = {
    {"X Axis", "Y Axis", "Z Axis", "X Offset", "Y Offset", "Z Offset"},
    {"Latitude", "Longitude", "Radius",
      "Cyl Radius", "Cyl Azimuth", "Cyl Z Axis"},
    {"Flow1 X", "Flow1 Y", "Flow1 Z", "Flow2 X", "Flow2 Y", "Flow2 Z"},
    {"Flow1 Elevation", "Flow1 Azimuth", "Flow1 Radial",
      "Flow2 Elevation", "Flow2 Azimuth", "Flow2 Radial"},
    {"Red", "Green", "Blue", "RGB", "RGBA", "Alpha"},
    {"Cyan", "Magenta", "Yellow", "CMY", "Animation", "Iso-contour"},
    {"Hue", "Saturation", "Value", "HSV", "Select Value", "Select Range"},
    {"", "Text", "", "", "Shape", ""}
  };

  /**
   * List of system intrinsic DisplayRealTypes.
   */
  private static final DisplayRealType[][] MapTypes = {
    {Display.XAxis, Display.YAxis, Display.ZAxis,
      Display.XAxisOffset, Display.YAxisOffset, Display.ZAxisOffset},
    {Display.Latitude, Display.Longitude, Display.Radius,
      Display.CylRadius, Display.CylAzimuth, Display.CylZAxis},
    {Display.Flow1X, Display.Flow1Y, Display.Flow1Z,
      Display.Flow2X, Display.Flow2Y, Display.Flow2Z},
    {Display.Flow1Elevation, Display.Flow1Azimuth, Display.Flow1Radial,
      Display.Flow2Elevation, Display.Flow2Azimuth, Display.Flow2Radial},
    {Display.Red, Display.Green, Display.Blue,
      Display.RGB, Display.RGBA, Display.Alpha},
    {Display.Cyan, Display.Magenta, Display.Yellow,
      Display.CMY, Display.Animation, Display.IsoContour},
    {Display.Hue, Display.Saturation, Display.Value,
      Display.HSV, Display.SelectValue, Display.SelectRange},
    {null, Display.Text, null, null, Display.Shape, null}
  };

  /**
   * Indices into MapTypes of alpha-related DisplayRealTypes.
   */
  private static final Point[] AlphaMaps = {
    new Point(4, 4), // RGBA
    new Point(4, 5)  // Alpha
  };

  /**
   * Indices into MapTypes of 3D-related DisplayRealTypes.
   */
  private static final Point[] ThreeDMaps = {
    new Point(0, 2), // ZAxis
    new Point(0, 5), // ZAxisOffset
    new Point(1, 0), // Latitude
    new Point(1, 5), // CylZAxis
    new Point(2, 2), // Flow1Z
    new Point(2, 5), // Flow2Z
    new Point(3, 0), // Flow1Elevation
    new Point(3, 3)  // Flow2Elevation
  };
  
  /**
   * Width of mapping arrays.
   */
  private static final int MapWidth = MapTypes[0].length;

  /**
   * Height of mapping arrays.
   */
  private static final int MapHeight = MapTypes.length;

  /**
   * display.gif image.
   */
  private static Image DRT = null;

  /**
   * Whether DRT image has been initialized.
   */
  private static boolean Inited = false;

  /**
   * Pre-loads the display.gif file, so it's ready
   * when mapping dialog is requested.
   */
  public static void initDialog() {
    if (DRT == null) {
      URL url = MappingDialog.class.getResource("display.gif");
      DRT = Toolkit.getDefaultToolkit().getImage(url);
    }
    Inited = true;
  }

  /**
   * Returns a human-readable list of CoordinateSystem dependencies.
   */
  private static String prettyCoordSys(MathType type) {
    String s = "";
    if (type instanceof FunctionType) s = s + pcsFunction((FunctionType) type);
    else if (type instanceof SetType) s = s + pcsSet((SetType) type);
    else if (type instanceof TupleType) s = s + pcsTuple((TupleType) type);
    return s;
  }

  /**
   * prettyCoordSys helper method.
   */
  private static String pcsFunction(FunctionType mathType) {
    String s = "";

    // extract domain
    RealTupleType domain = mathType.getDomain();
    s = s + pcsTuple((TupleType) domain);

    // extract range
    MathType range = mathType.getRange();
    s = s + prettyCoordSys(range);

    return s;
  }

  /**
   * prettyCoordSys helper method.
   */
  private static String pcsSet(SetType mathType) {
    // extract domain
    RealTupleType domain = mathType.getDomain();
    return pcsTuple((TupleType) domain);
  }

  /**
   * prettyCoordSys helper method.
   */
  private static String pcsTuple(TupleType mathType) {
    String s = "";
    if (mathType instanceof RealTupleType) {
      RealTupleType rtt = (RealTupleType) mathType;
      CoordinateSystem cs = rtt.getCoordinateSystem();
      if (cs != null) {
        RealTupleType ref = cs.getReference();
        if (ref != null) {
          s = s + rtt.prettyString() + " ==> " + ref.prettyString() + "\n";
        }
      }
    }
    else {
      // extract components
      for (int j=0; j width) width = w;
      pos = nextPos;
      numLines++;
    }
    while (pos < typeString.length());
    StrSize = new Dimension(width, (ScH + 2) * numLines + 10);

    // get starting line position for each line of MathType string
    int[] mathLines = new int[numLines];
    pos = -1;
    for (int i=0; i width) width = w;
      pos = nextPos;
      numLines++;
    }
    while (pos < coordString.length() - 1);
    CoordSize = new Dimension(width, (ScH + 2) * numLines + 10);

    // get starting line position for each line of CoordinateSystem string
    int[] coordLines = new int[numLines];
    pos = -1;
    for (int i=0; i= 0) {
          for (int i=0; i maxMCHeight) prefMCHeight = maxMCHeight;
    MathCanvasView.setPreferredSize(new Dimension(0, prefMCHeight));
    MathCanvasView.setBackground(Color.white);
    JScrollBar horiz = MathCanvasView.getHorizontalScrollBar();
    JScrollBar verti = MathCanvasView.getVerticalScrollBar();
    horiz.setBlockIncrement(5 * ScH + 10);
    horiz.setUnitIncrement(ScH+2);
    verti.setBlockIncrement(5 * ScH + 10);
    verti.setUnitIncrement(ScH+2);
    topPanel.add(Box.createRigidArea(new Dimension(5, 0)));
    JPanel whitePanel = new JPanel();
    whitePanel.setBackground(Color.white);
    whitePanel.setLayout(new BoxLayout(whitePanel, BoxLayout.X_AXIS));
    whitePanel.add(MathCanvasView);
    topPanel.add(whitePanel);
    topPanel.add(Box.createRigidArea(new Dimension(5, 0)));

    // set up description label
    description = new JLabel(" ");
    JPanel descPanel = new JPanel();
    descPanel.setLayout(new BoxLayout(descPanel, BoxLayout.X_AXIS));
    contentPane.add(descPanel);
    contentPane.add(Box.createRigidArea(new Dimension(0, 15)));
    descPanel.add(description);

    // set up lower panel
    JPanel lowerPanel = new JPanel();
    lowerPanel.setLayout(new BoxLayout(lowerPanel, BoxLayout.X_AXIS));
    contentPane.add(lowerPanel);
    contentPane.add(Box.createRigidArea(new Dimension(0, 5)));

    // set up far-left-side panel
    JPanel flsPanel = new JPanel();
    flsPanel.setLayout(new BoxLayout(flsPanel, BoxLayout.Y_AXIS));
    lowerPanel.add(Box.createRigidArea(new Dimension(5, 0)));
    lowerPanel.add(flsPanel);

    // only do CoordSys stuff if there are CoordinateSystem references
    if (CoordRefs) {
      // draw the pretty-print CoordinateSystem references to an Image (slow!)
      final BufferedImage csImg = new BufferedImage(
        CoordSize.width, CoordSize.height, BufferedImage.TYPE_INT_RGB);
      g = csImg.getGraphics();
      g.setFont(Mono);
      g.setColor(Color.white);
      g.fillRect(0, 0, CoordSize.width, CoordSize.height);
      g.setColor(Color.black);
      for (int i=0; i= 0) {
            for (int i=0; i maxCCHeight) prefCCHeight = maxCCHeight;
      CoordCanvasView.setPreferredSize(new Dimension(0, prefCCHeight));
      CoordCanvasView.setBackground(Color.white);
      horiz = CoordCanvasView.getHorizontalScrollBar();
      verti = CoordCanvasView.getVerticalScrollBar();
      horiz.setBlockIncrement(5 * ScH + 10);
      horiz.setUnitIncrement(ScH + 2);
      verti.setBlockIncrement(5 * ScH + 10);
      verti.setUnitIncrement(ScH + 2);
      topPanel2.add(Box.createRigidArea(new Dimension(5, 0)));
      JPanel whitePanel2 = new JPanel();
      whitePanel2.setBackground(Color.white);
      whitePanel2.setLayout(new BoxLayout(whitePanel2, BoxLayout.X_AXIS));
      whitePanel2.add(CoordCanvasView);
      topPanel2.add(whitePanel2);
      topPanel2.add(Box.createRigidArea(new Dimension(5, 0)));
    }

    // set up left-side panel
    JPanel lsPanel = new JPanel();
    lsPanel.setLayout(new BoxLayout(lsPanel, BoxLayout.Y_AXIS));
    lowerPanel.add(Box.createRigidArea(new Dimension(5, 0)));
    lowerPanel.add(lsPanel);

    // begin set up "current mappings" list
    CurMaps = new DefaultListModel();
    int num = MathTypes.length;
    CurMaps.ensureCapacity(num * MapWidth * MapHeight);
    CurrentMaps = new JList(CurMaps);

    // set up "map from" list
    Maps = new boolean[num][MapHeight][MapWidth];
    CurMapLabel = new String[num][MapHeight][MapWidth];
    for (int i=0; i " + MapNames[j][k];
          if (startMaps != null) {
            for (int m=0; m= 0) {
          for (int col=0; col screen.width - pad) w = screen.width - pad;
    if (h > screen.height - pad) h = screen.height - pad;
    return new Dimension(w, h);
  }

  /**
   * Displays the dialog in the center of the screen.
   */
  public void display() {
    pack();
    Util.centerWindow(this);
    setVisible(true);
  }

  /**
   * Gets the mappings selected by the user.
   */
  public ScalarMap[] getMaps() {
    return ScalarMaps;
  }

  /**
   * Gets whether the user pressed the Ok button.
   */
  public boolean okPressed() {
    return Confirm;
  }

  /**
   * Recursive quick-sort routine for alphabetizing scalars.
   */
  private void sort(int lo0, int hi0) {
    if (hi0 < lo0) return;
    int lo = lo0;
    int hi = hi0;
    String mid = Scalars[(lo0 + hi0) / 2].toLowerCase();
    while (lo <= hi) {
      while (lo < hi0 && Scalars[lo].toLowerCase().compareTo(mid) < 0) ++lo;
      while (hi > lo0 && Scalars[hi].toLowerCase().compareTo(mid) > 0) --hi;
      if (lo <= hi) {
        // MathTypes[lo] <-> MathTypes[hi]
        ScalarType st = MathTypes[lo];
        MathTypes[lo] = MathTypes[hi];
        MathTypes[hi] = st;
        // Scalars[lo] <-> Scalars[hi]
        String s = Scalars[lo];
        Scalars[lo] = Scalars[hi];
        Scalars[hi] = s;
        // ScW[lo] <-> ScW[hi]
        int i = ScW[lo];
        ScW[lo] = ScW[hi];
        ScW[hi] = i;
        // ScP[lo] <-> ScP[hi]
        Vector v = ScP[lo];
        ScP[lo] = ScP[hi];
        ScP[hi] = v;

        lo++;
        hi--;
      }
    }
    if (lo0 < hi) sort(lo0, hi);
    if (lo < hi0) sort(lo, hi0);
  }

  /**
   * Clears a box in the "map to" canvas.
   */
  void eraseBox(int col, int row, Graphics g) {
    int x = 40 * col;
    int y = 40 * row;
    g.setColor(Color.black);
    for (int i=0; i<40; i+=2) {
      g.drawLine(x, y + i, x + i, y);
      g.drawLine(x + i, y + 38, x + 38, y + i);
    }
  }

  /**
   * Highlights a box in the "map to" canvas.
   */
  void highlightBox(int col, int row, Graphics g) {
    int x = 40 * col;
    int y = 40 * row;
    final int n1 = 11;
    final int n2 = 29;
    g.setColor(Color.blue);
    g.drawRect(x, y, 39, 39);
    g.drawLine(x, y + n1, x + n1, y);
    g.drawLine(x, y + n2, x + n2, y);
    g.drawLine(x + n1, y + 39, x + 39, y + n1);
    g.drawLine(x + n2, y + 39, x + 39, y + n2);
    g.drawLine(x + n2, y, x + 39, y + n1);
    g.drawLine(x + n1, y, x + 39, y + n2);
    g.drawLine(x, y + n1, x + n2, y + 39);
    g.drawLine(x, y + n2, x + n1, y + 39);
  }

  /**
   * Clears all maps from the current mappings list.
   */
  private void clearAll() {
    CurrentMaps.clearSelection(); // work-around for nasty swing bug
    CurMaps.removeAllElements();
    for (int i=0; i= 0) setType = setType.substring(index + 1);
        int dim = set.getDimension();
        s = setType + "(" + dim + ")";
      }
      desc = desc + ": Unit=" + u + "; Set=" + s;
    }
    else {
      desc = desc + " (text)";
    }
    description.setText(desc);
  }

  /**
   * Handles button press events.
   */
  public void actionPerformed(ActionEvent e) {
    String cmd = e.getActionCommand();

    synchronized (Lock) {
      if (cmd.equals("all")) { // clear all
        if (CurMaps.getSize() > 0) {
          clearAll();
          // update components
          Graphics g = DisplayCanvas.getGraphics();
          DisplayCanvas.paint(g);
          g.dispose();
          CurrentMaps.repaint();
          CurrentMapsView.validate();
        }
      }

      else if (cmd.equals("sel")) { // clear selected
        int[] ndx = CurrentMaps.getSelectedIndices();
        int len = ndx.length;
        for (int x=len-1; x>=0; x--) {
          String s = (String) CurMaps.getElementAt(ndx[x]);
          boolean looking = true;
          for (int i=0; i 0) {
          // update components
          Graphics g = DisplayCanvas.getGraphics();
          DisplayCanvas.paint(g);
          g.dispose();
          CurrentMaps.repaint();
          CurrentMapsView.validate();
        }
      }

      else if (cmd.equals("detect")) {
        ScalarMap[] maps = DataUtility.guessMaps(Types, Allow3D);
        if (CurMaps.getSize() > 0) clearAll();
        if (maps != null) {
          for (int i=0; i " + MapNames[j][k] + ") is not valid.",
                    "Illegal mapping", JOptionPane.ERROR_MESSAGE);
                }
              }
            }
          }
        }
        if (okay) {
          Confirm = true;
          setVisible(false);
        }
      }

      else if (cmd.equals("cancel")) setVisible(false);
    }
  }

  /**
   * Handles list selection change events.
   */
  public void valueChanged(ListSelectionEvent e) {
    synchronized (Lock) {
      if (!e.getValueIsAdjusting()) {
        if ((JList) e.getSource() == MathList) {
          Graphics g = DisplayCanvas.getGraphics();
          DisplayCanvas.paint(g);
          g.dispose();
          int i = MathList.getSelectedIndex();
          MDTuple tuple = (MDTuple) ScP[i].elementAt(0);
          Rectangle r = new Rectangle(tuple.x, tuple.y, ScW[i], ScH);
          MathList.ensureIndexIsVisible(i);
          if (tuple.b) CoordCanvas.scrollRectToVisible(r);
          else MathCanvas.scrollRectToVisible(r);
          MathCanvas.repaint();
          if (CoordRefs) CoordCanvas.repaint();
          updateDescriptionLabel(i);
        }
      }
    }
  }

  /**
   * Handles mouse clicks in the MathType window and "map to" canvas.
   */
  public void mousePressed(MouseEvent e) {
    synchronized (Lock) {
      Component c = e.getComponent();
      if (c == MathCanvas) {
        Point p = e.getPoint();
        for (int i=0; i= 0 && row >= 0 && col >= 0 &&
          row < MapHeight && col < MapWidth && !Illegal[row][col])
        {
          Maps[ndx][row][col] = !Maps[ndx][row][col];
          if (Maps[ndx][row][col]) {
            CurMaps.addElement(CurMapLabel[ndx][row][col]);
          }
          else {
            CurrentMaps.clearSelection();
            CurMaps.removeElement(CurMapLabel[ndx][row][col]);
          }
          // redraw DisplayCanvas
          Graphics g = DisplayCanvas.getGraphics();
          g.setClip(40 * col, 40 * row, 41, 41);
          DisplayCanvas.paint(g);
          g.dispose();

          // redraw CurrentMaps
          CurrentMaps.repaint();
          CurrentMapsView.validate();
        }
      }
      else if (c == CoordCanvas) {
        Point p = e.getPoint();
        for (int i=0; i 

Ads help maintain this website.