//
// SpreadSheet.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.event.*;
import java.awt.print.*;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.rmi.*;
import java.rmi.registry.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import visad.*;
import visad.data.Form;
import visad.data.netcdf.Plain;
import visad.data.tiff.TiffForm;
import visad.data.visad.VisADForm;
import visad.formula.*;
import visad.util.*;

/**
 * SpreadSheet is a user interface for VisAD that supports
 * multiple 3-D displays (FancySSCells).
 */
public class SpreadSheet extends GUIFrame implements AdjustmentListener,
  DisplayListener, KeyListener, ItemListener, MouseListener,
  MouseMotionListener, SSCellListener
{

  /**
   * Starting width of the application, in percentage of screen size.
   */
  protected static final int WIDTH_PERCENT = 60;

  /**
   * Starting width of the application, in percentage of screen size.
   */
  protected static final int HEIGHT_PERCENT = 80;

  /**
   * Minimum VisAD display width, including display border.
   */
  protected static final int MIN_VIS_WIDTH = 120;

  /**
   * Minimum VisAD display height, including display border.
   */
  protected static final int MIN_VIS_HEIGHT = 120;

  /**
   * Default VisAD display width.
   */
  protected static final int DEFAULT_VIS_WIDTH = 250;

  /**
   * Default VisAD display height.
   */
  protected static final int DEFAULT_VIS_HEIGHT = 250;

  /**
   * Spreadsheet cell letter order.
   */
  protected static final String Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

  /**
   * Vertical cell label's width.
   */
  protected static final int LABEL_WIDTH = 30;

  /**
   * Horizontal cell label's height.
   */
  protected static final int LABEL_HEIGHT = 20;

  /**
   * Whether connection status messages are printed about clones.
   */
  protected static final boolean SHOW_CONNECT_MESSAGES = true;

  /**
   * Header for first line of spreadsheet files.
   */
  protected static final String SSFileHeader =
    "# VisAD Visualization SpreadSheet spreadsheet file";

  /**
   * Argument classes for constructing an SSCell.
   */
  protected static final Class[] cellArgs = {
    String.class, FormulaManager.class, RemoteServer.class,
    boolean.class, String.class, Frame.class
  };


  /**
   * Constructor used to create SSCells for SpreadSheets.
   */
  protected static Constructor cellConstructor;

  /**
   * Whether Java3D is possible on this JVM.
   */
  protected static boolean Possible3D;

  /**
   * Whether Java3D is enabled on this JVM.
   */
  protected static boolean CanDo3D;

  /**
   * Whether the HDF-5 native library is present on this JVM.
   */
  protected static boolean CanDoHDF5;

  /**
   * Whether this JVM supports saving JPEG images with JPEGImageEncoder.
   */
  protected static boolean CanDoJPEG;

  /**
   * Whether this JVM supports JPython scripting.
   */
  protected static boolean CanDoPython;

  /**
   * Whether spreadsheet should have toolbar buttons.
   */
  protected static boolean BugFix;


  /**
   * File dialog.
   */
  protected JFileChooser SSFileDialog;

  /**
   * Base title.
   */
  protected String bTitle;

  /**
   * Number of display columns.
   */
  protected int NumVisX;

  /**
   * Number of display rows.
   */
  protected int NumVisY;

  /**
   * Formula manager.
   */
  protected FormulaManager fm;


  /**
   * Server name, if any.
   */
  protected String serverName;

  /**
   * Server address for a cloned sheet, if any.
   */
  protected String cloneAddress;

  /**
   * Server for spreadsheet cells, if any.
   */
  protected RemoteServerImpl rsi = null;

  /**
   * Whether spreadsheet is a clone of another spreadsheet.
   */
  protected boolean IsRemote = false;

  /**
   * Whether spreadsheet is a slaved clone of another spreadsheet.
   */
  protected boolean IsSlave = false;

  /**
   * ID number for this collaborative spreadsheet.
   */
  protected double CollabID = 0;

  /**
   * Row and column information needed for spreadsheet cloning.
   */
  protected RemoteDataReference RemoteColRow;

  /**
   * Remote clone's copy of CanDo3D.
   */
  protected RemoteDataReference RemoteCanDo3D;


  /**
   * Flag marking whether spreadsheet's cells
   * automatically switch dimensions when needed.
   */
  protected boolean AutoSwitch = true;

  /**
   * Flag marking whether spreadsheet's cells
   * automatically detect mappings.
   */
  protected boolean AutoDetect = true;

  /**
   * Flag marking whether spreadsheet's cells
   * automatically show controls.
   */
  protected boolean AutoShowControls = true;


  /**
   * Panel that contains actual VisAD displays.
   */
  protected Panel DisplayPanel;

  /**
   * Panel containing the scrolling pane.
   */
  protected JPanel ScrollPanel;

  /**
   * Scrolling pane, in case sheet gets too small.
   */
  protected ScrollPane SCPane;

  /**
   * View port for horizontal cell labels.
   */
  protected JViewport HorizLabels;

  /**
   * View port for vertical cell labels.
   */
  protected JViewport VertLabels;

  /**
   * Array of panels for horizontal labels.
   */
  protected JPanel[] HorizLabel;

  /**
   * Array of panels for vertical labels.
   */
  protected JPanel[] VertLabel;

  /**
   * Array of horizontal yellow sizing boxes.
   */
  protected JComponent[] HorizDrag;

  /**
   * Array of vertical yellow sizing boxes.
   */
  protected JComponent[] VertDrag;

  /**
   * Panel containing horizontal labels and sizing boxes.
   */
  protected JPanel HorizPanel;

  /**
   * Panel containing vertical labels and sizing boxes.
   */
  protected JPanel VertPanel;

  /**
   * Array of spreadsheet cells.
   */
  protected FancySSCell[][] DisplayCells = null;

  /**
   * Formula bar.
   */
  protected JComboBox FormulaBox;

  /**
   * Formula editor.
   */
  protected ComboBoxEditor FormulaEditor;

  /**
   * Formula text field.
   */
  protected JTextField FormulaText;

  /**
   * Formula action listener.
   */
  protected ActionListener FormulaListener;


  /**
   * Tool bar.
   */
  protected JToolBar Toolbar;

  /**
   * Submenus.
   */
  protected JMenu FileExport;
   
  /**
   * Menu items.
   */
  protected JMenuItem FileSave1, FileSave2, FileSave3, FileSave4, FileSave5,
    FileSnap, EditPaste, EditClear, CellDel, CellPrint, CellEdit, CellReset,
    CellShow, LayAddCol, LayDelCol, LayDelRow;

  /**
   * Checkbox menu items.
   */
  protected JCheckBoxMenuItem CellDim3D3D, CellDim2D2D, CellDim2D3D,
    AutoSwitchBox, AutoDetectBox, AutoShowBox;

  /**
   * Toolbar buttons.
   */
  protected JButton ToolSave, ToolPaste, Tool3D, Tool2D, ToolJ2D, ToolMap,
    ToolShow, ToolReset, FormulaAdd, FormulaDel;


  /**
   * Column of currently selected cell.
   */
  protected int CurX = 0;

  /**
   * Row of currently selected cell.
   */
  protected int CurY = 0;

  /**
   * Contents of clipboard.
   */
  protected String Clipboard = null;

  /**
   * Current spreadsheet file.
   */
  protected File CurrentFile = null;

  /**
   * Object for preventing simultaneous GUI manipulation.
   */
  protected Object Lock = new Object();


  /**
   * Waits the specified number of milliseconds.
   */
  public static void snooze(long ms) {
    try {
      Thread.sleep(ms);
    }
    catch (InterruptedException exc) {
      if (BasicSSCell.DEBUG) exc.printStackTrace();
    }
  }

  /**
   * Gateway into VisAD Visualization SpreadSheet user interface.
   */
  public static void main(String[] argv) {
    String usage = "\n" +
      "Usage: java [-mx###m] visad.ss.SpreadSheet [cols rows]\n" +
      "       [-file filename] [-gui] [-no3d] [-debug] [-bugfix]\n" +
      "       [-server name] [-client address] [-slave address]\n\n" +
      "###\n" +
      "     Maximum megabytes of memory to use.\n" +
      "cols\n" +
      "     Number of columns in this SpreadSheet.\n" +
      "rows\n" +
      "     Number of rows in this SpreadSheet.\n" +
      "-file filename\n" +
      "     Load the given filename at launch. If file is a\n" +
      "     spreadsheet file, the layout is configured accordingly.\n" +
      "     If file is data, it is loaded into cell A1.\n" +
      "-gui\n" +
      "     Pop up an options window so that the user can\n" +
      "     select SpreadSheet settings graphically.\n" +
      "-no3d\n" +
      "     Disable Java3D.\n" +
      "-debug\n" +
      "     Print stack traces for all errors.\n" +
      "-bugfix\n" +
      "     Disable toolbar. For some systems, will prevent\n" +
      "     lockups on spreadsheet start.\n" +
      "-server name\n" +
      "     Initialize this SpreadSheet as an RMI server\n" +
      "     with the given name.\n" +
      "-client address\n" +
      "     Initialize this SpreadSheet as a clone of\n" +
      "     the server at the given RMI address.\n" +
      "-slave address\n" +
      "     Initialize this SpreadSheet as a slaved clone\n" +
      "     of the server at the given RMI address.";
    int cols = 2;
    int rows = 2;
    String dfile = null;
    String servname = null;
    String clonename = null;
    boolean guiOptions = false;
    int len = argv.length;
    if (len > 0) {
      int ix = 0;

      // parse command line flags
      while (ix < len) {
        if (argv[ix].charAt(0) == '-') {
          if (argv[ix].equals("-file")) {
            if (ix < len - 1) dfile = argv[++ix];
          }
          else if (argv[ix].equals("-server")) {
            if (clonename != null) {
              System.out.println("A spreadsheet cannot be both a server " +
                "and a clone!");
              System.out.println(usage);
              System.exit(3);
            }
            else if (ix < len - 1) servname = argv[++ix];
            else {
              System.out.println("You must specify a server name after " +
                "the '-server' flag!");
              System.out.println(usage);
              System.exit(4);
            }
          }
          else if (argv[ix].equals("-client") || argv[ix].equals("-slave")) {
            if (servname != null) {
              System.out.println("A spreadsheet cannot be both a server " +
                "and a clone!");
              System.out.println(usage);
              System.exit(3);
            }
            else if (ix < len - 1) {
              clonename = argv[ix + 1];
              if (argv[ix].equals("-slave")) clonename = "slave:" + clonename;
              ix++;
            }
            else {
              System.out.println("You must specify a server after " +
                "the '" + argv[ix] + "' flag!");
              System.out.println(usage);
              System.exit(5);
            }
          }
          else if (argv[ix].equals("-gui")) guiOptions = true;
          else if (argv[ix].equals("-bugfix")) BugFix = true;
          else if (argv[ix].equals("-no3d")) BasicSSCell.disable3D();
          else if (argv[ix].equals("-debug")) {
            BasicSSCell.DEBUG = true;
            FormulaVar.DEBUG = true;
          }
          else {
            // unknown flag
            if (!argv[ix].equals("-help")) {
              System.out.println("Unknown option: " + argv[ix]);
            }
            System.out.println(usage);
            System.exit(1);
          }
        }
        else {
          // parse number of rows and columns
          boolean success = true;
          if (ix < len - 1) {
            try {
              cols = Integer.parseInt(argv[ix]);
              rows = Integer.parseInt(argv[ix + 1]);
              ix++;
              if (rows < 1 || cols < 1 || cols > Letters.length()) {
                success = false;
              }
            }
            catch (NumberFormatException exc) {
              success = false;
            }
            if (!success) {
              System.out.println("Invalid number of columns and rows: " +
                argv[ix] + " x " + argv[ix + 1]);
              System.out.println(usage);
              System.exit(2);
            }
          }
          else {
            System.out.println("Unknown option: " + argv[ix]);
            System.out.println(usage);
            System.exit(1);
          }
        }
        ix++;
      }
    }
    final SpreadSheet ss = new SpreadSheet(WIDTH_PERCENT, HEIGHT_PERCENT,
      cols, rows, servname, clonename, "VisAD SpreadSheet", null, guiOptions);
    if (dfile != null) {
      File f = new File(dfile);
      String line = null;
      try {
        BufferedReader fin = new BufferedReader(new FileReader(f));
        line = fin.readLine();
        fin.close();
      }
      catch (IOException exc) {
        if (BasicSSCell.DEBUG) exc.printStackTrace();
        System.out.println("Could not read file " + dfile);
      }
      if (line != null) {
        final boolean ssfile = line.equals(SSFileHeader);
        final String filename = dfile;
        Util.invoke(false, BasicSSCell.DEBUG, new Runnable() {
          public void run() {
            try {
              if (ssfile) {
                // file is a spreadsheet file
                ss.openFile(filename);
              }
              else {
                // file is a data file
                ss.DisplayCells[0][0].addDataSource(filename);
              }
            }
            catch (Exception exc) {
              if (BasicSSCell.DEBUG) exc.printStackTrace();
              System.out.println("Could not load file " + filename +
                " into the SpreadSheet");
            }
          }
        });
      }
    }
  }


  // --- CONSTRUCTORS ---

  /**
   * Constructor with option selection dialog at default values.
   */
  public SpreadSheet() {
    this(WIDTH_PERCENT, HEIGHT_PERCENT, 2, 2, null, null,
      "VisAD SpreadSheet", null, true);
  }

  /**
   * Constructor with default formula manager and no option selection dialog.
   */
  public SpreadSheet(int sWidth, int sHeight, int cols, int rows,
    String server, String clone, String sTitle)
  {
    this(sWidth, sHeight, cols, rows, server, clone, sTitle, null, false);
  }

  /**
   * Constructor with no option selection dialog.
   */
  public SpreadSheet(int sWidth, int sHeight, int cols, int rows,
    String server, String clone, String sTitle, FormulaManager fm)
  {
    this(sWidth, sHeight, cols, rows, server, clone, sTitle, fm, false);
  }

  /**
   * Main constructor.
   */
  public SpreadSheet(int sWidth, int sHeight, int cols, int rows,
    String server, String clone, String sTitle, FormulaManager fm,
    boolean chooseOptions)
  {
    super(true);
    bTitle = sTitle;
    NumVisX = cols;
    NumVisY = rows;
    this.fm = fm;
    Possible3D = BasicSSCell.possible3D();
    CanDo3D = BasicSSCell.canDo3D();
    MappingDialog.initDialog();
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        quitProgram();
      }
    });
    setBackground(Color.white);

    // parse clone address
    boolean slave = clone != null && clone.startsWith("slave:");
    if (slave) clone = clone.substring(6);
    if (clone != null) {
      int slash = clone.lastIndexOf("/");
      if (slash < 0) slash = clone.lastIndexOf(":");
      server = clone.substring(slash + 1);
      clone = clone.substring(0, slash);
    }
    serverName = server;
    cloneAddress = clone;
    IsSlave = slave;

    // test whether HDF-5 native library is present
    CanDoHDF5 = Util.canDoHDF5();
    if (!CanDoHDF5 && BasicSSCell.DEBUG) {
      System.err.println("Warning: HDF-5 library not found");
    }

    // test whether JPEG codec is present
    CanDoJPEG = Util.canDoJPEG();
    if (!CanDoJPEG && BasicSSCell.DEBUG) {
      System.err.println("Warning: JPEG codec not found");
    }

    // test whether JPython is present
    CanDoPython = Util.canDoPython();
    if (!CanDoPython && BasicSSCell.DEBUG) {
      System.err.println("Warning: JPython not found");
    }

    // create file chooser dialog
    SSFileDialog = new JFileChooser(System.getProperty("user.dir"));
    SSFileDialog.addChoosableFileFilter(
      new ExtensionFileFilter("ss", "SpreadSheet files"));

    if (chooseOptions) {
      // get settings from option selection dialog
      getOptions(NumVisX, NumVisY, serverName, cloneAddress, IsSlave);
    }
    clone = cloneAddress == null ? null : cloneAddress + "/" + serverName;
    server = cloneAddress == null ? serverName : null;

    // determine information for spreadsheet cloning
    RemoteServer rs = null;
    String[][] cellNames = null;
    if (clone != null) {
      // CLIENT: initialize

      // connect to server
      boolean success = true;
      if (SHOW_CONNECT_MESSAGES) {
        System.out.print("Connecting to " + clone + " ");
      }

      // connection loop
      while (cellNames == null && success) {
        // wait a second before trying to connect
        snooze(1000);
        if (SHOW_CONNECT_MESSAGES) System.out.print(".");

        try {
          // look up server
          rs = (RemoteServer) Naming.lookup("//" + clone);

          // determine whether server supports Java3D
          RemoteCanDo3D = rs.getDataReference("CanDo3D");
          Real bit = (Real) RemoteCanDo3D.getData();
          if (bit.getValue() == 0) {
            CanDo3D = false;
            BasicSSCell.disable3D();
          }

          // extract cell name information
          RemoteColRow = rs.getDataReference("ColRow");
          cellNames = getNewCellNames();
        }
        catch (UnmarshalException exc) {
          // fatal RMI error, probably a version difference; display error box
          if (BasicSSCell.DEBUG) exc.printStackTrace();
          displayErrorMessage("Unable to clone the spreadsheet at " + clone +
            ". The server is using an incompatible version of Java", null,
            "Failed to clone spreadsheet");
          success = false;
        }
        catch (MalformedURLException exc) {
          // server name is invalid; display error box
          if (BasicSSCell.DEBUG) exc.printStackTrace();
          displayErrorMessage("Unable to clone the spreadsheet at " + clone +
            ". The server name is not valid", null,
            "Failed to clone spreadsheet");
          success = false;
        }
        catch (VisADException exc) {
          // fatal error of some other type; display error box
          if (BasicSSCell.DEBUG) exc.printStackTrace();
          displayErrorMessage("Unable to clone the spreadsheet at " + clone +
            ". An error occurred while downloading the necessary data", exc,
            "Failed to clone spreadsheet");
          success = false;
        }
        catch (NotBoundException exc) {
          // server is not ready yet; try again
          if (BasicSSCell.DEBUG) exc.printStackTrace();
        }
        catch (NullPointerException exc) {
          // server is not ready yet; try again
          if (BasicSSCell.DEBUG) exc.printStackTrace();
        }
        catch (RemoteException exc) {
          // server is not ready yet; try again
          if (BasicSSCell.DEBUG) exc.printStackTrace();
        }
      }

      if (success) {
        if (SHOW_CONNECT_MESSAGES) System.out.println(" done");
        bTitle = bTitle + " [" + (IsSlave ? "slaved" : "collaborative") +
          " mode: " + clone + "]";
        IsRemote = true;
      }
      else {
        if (SHOW_CONNECT_MESSAGES) System.out.println(" failed");
        IsSlave = false;
        rs = null;
      }
    }

    // set up the content pane
    JPanel pane = new JPanel();
    pane.setBackground(Color.white);
    pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
    setContentPane(pane);

    // set up file menu
    addMenuItem("File", "Import data...", "loadDataSet", 'i');
    FileExport = addSubMenu("File", "Export data", 'e', false);
    FileSave1 = addMenuItem("Export data", "netCDF...",
      "exportDataSetNetcdf", 'n', true);
    FileSave3 = addMenuItem("Export data", "HDF-5...",
      "exportDataSetHDF5", 'h', CanDoHDF5);
    FileSave4 = addMenuItem("Export data", "TIFF...",
      "exportDataSetTIFF", 't', true);
    FileSave2 = addMenuItem("Export data", "Serialized...",
      "exportDataSetSerial", 's', true);
    FileSave5 = addMenuItem("Export data", "Binary...",
      "exportDataSetBinary", 'b', true);
    addMenuSeparator("File");
    FileSnap = addMenuItem("File", "Take JPEG snapshot...",
      "captureImageJPEG", 'j', false);
    addMenuSeparator("File");
    addMenuItem("File", "Exit", "quitProgram", 'x');

    // set up edit menu
    addMenuItem("Edit", "Cut", "cutCell", 't', !IsRemote);
    addMenuItem("Edit", "Copy", "copyCell", 'c', !IsRemote);
    EditPaste = addMenuItem("Edit", "Paste", "pasteCell", 'p', false);
    EditClear = addMenuItem("Edit", "Clear", "clearCell", 'l', false);

    // set up setup menu
    addMenuItem("Setup", "New spreadsheet file", "newFile", 'n');
    addMenuItem("Setup", "Open spreadsheet file...", "openFile", 'o',
      !IsRemote);
    addMenuItem("Setup", "Save spreadsheet file", "saveFile", 's', !IsRemote);
    addMenuItem("Setup", "Save spreadsheet file as...", "saveAsFile", 'a',
      !IsRemote);

    // set up cell menu
    CellDim3D3D = new JCheckBoxMenuItem("3-D (Java3D)", CanDo3D);
    addMenuItem("Cell", CellDim3D3D, "setDim3D", '3', CanDo3D);
    CellDim2D2D = new JCheckBoxMenuItem("2-D (Java2D)", !CanDo3D);
    addMenuItem("Cell", CellDim2D2D, "setDimJ2D", 'j', true);
    CellDim2D3D = new JCheckBoxMenuItem("2-D (Java3D)", false);
    addMenuItem("Cell", CellDim2D3D, "setDim2D", '2', CanDo3D);
    addMenuSeparator("Cell");
    addMenuItem("Cell", "Add data object", "formulaAdd", 'a');
    CellDel = addMenuItem("Cell", "Delete data object",
      "formulaDel", 'd', false);
    addMenuSeparator("Cell");
    CellPrint = addMenuItem("Cell", "Print cell...",
      "printCurrentCell", 'p', false);
    addMenuSeparator("Cell");
    CellEdit = addMenuItem("Cell", "Edit mappings...",
      "createMappings", 'e', false);
    CellReset = addMenuItem("Cell", "Reset orientation",
      "resetOrientation", 'r', false);
    CellShow = addMenuItem("Cell", "Show controls",
      "showControls", 's', false);

    // set up layout menu
    LayAddCol = addMenuItem("Layout", "Add column", "addColumn", 'c');
    addMenuItem("Layout", "Add row", "addRow", 'r');
    LayDelCol = addMenuItem("Layout", "Delete column",
      "deleteColumn", 'l', NumVisX > 1);
    LayDelRow = addMenuItem("Layout", "Delete row",
      "deleteRow", 'w', NumVisY > 1);
    addMenuSeparator("Layout");
    addMenuItem("Layout", "Tile cells", "tileCells", 't');

    // set up options menu
    if (!CanDo3D) AutoSwitch = false;
    AutoSwitchBox = new JCheckBoxMenuItem("Auto-switch to 3-D",
      AutoSwitch && !IsRemote);
    addMenuItem("Options", AutoSwitchBox,
      "optionsSwitch", '3', CanDo3D && !IsRemote);
    AutoDetectBox = new JCheckBoxMenuItem("Auto-detect mappings",
      AutoDetect && !IsRemote);
    addMenuItem("Options", AutoDetectBox, "optionsDetect", 'm', !IsRemote);
    AutoShowBox = new JCheckBoxMenuItem("Auto-display controls",
      AutoShowControls && !IsSlave);
    addMenuItem("Options", AutoShowBox, "optionsDisplay", 'c', !IsSlave);

    // set up toolbar
    if (!BugFix) {
      Toolbar = new JToolBar();
      Toolbar.setBackground(Color.lightGray);
      Toolbar.setBorder(new EtchedBorder());
      Toolbar.setFloatable(false);
      pane.add(Toolbar);

      // file menu toolbar icons
      addToolbarButton("open", "Import data", "loadDataSet", true, Toolbar);
      ToolSave = addToolbarButton("save", "Export data to netCDF",
        "exportDataSetNetcdf", false, Toolbar);
      Toolbar.addSeparator();

      // edit menu toolbar icons
      addToolbarButton("cut", "Cut", "cutCell", !IsRemote, Toolbar);
      addToolbarButton("copy", "Copy", "copyCell", !IsRemote, Toolbar);
      ToolPaste = addToolbarButton("paste", "Paste",
        "pasteCell", false, Toolbar);
      Toolbar.addSeparator();

      // cell menu toolbar icons
      Tool3D = addToolbarButton("3d", "3-D (Java3D)",
        "setDim3D", false, Toolbar);
      ToolJ2D = addToolbarButton("j2d", "2-D (Java2D)",
        "setDimJ2D", CanDo3D, Toolbar);
      Tool2D = addToolbarButton("2d", "2-D (Java3D)",
        "setDim2D", CanDo3D, Toolbar);
      Toolbar.addSeparator();
      ToolMap = addToolbarButton("mappings", "Edit mappings",
        "createMappings", false, Toolbar);
      ToolReset = addToolbarButton("reset", "Reset orientation",
        "resetOrientation", false, Toolbar);
      ToolShow = addToolbarButton("show", "Show controls",
        "showControls", false, Toolbar);
      Toolbar.addSeparator();

      // layout menu toolbar icon
      addToolbarButton("tile", "Tile cells", "tileCells", true, Toolbar);
      Toolbar.add(Box.createHorizontalGlue());

      // HACK - limit toolbar height
      Toolbar.setMaximumSize(new Dimension(Toolbar.getMaximumSize().width,
        Toolbar.getPreferredSize().height));
    }

    // set up formula bar
    JPanel formulaPanel = new JPanel();
    formulaPanel.setPreferredSize(new Dimension(Integer.MAX_VALUE, 25));
    formulaPanel.setBackground(Color.lightGray);
    formulaPanel.setLayout(new BoxLayout(formulaPanel, BoxLayout.X_AXIS));
    formulaPanel.setBorder(new EtchedBorder());
    pane.add(formulaPanel);
    pane.add(Box.createRigidArea(new Dimension(0, 6)));
    if (!BugFix) {
      FormulaAdd = addToolbarButton("add", "Add data",
        "formulaAdd", true, formulaPanel);
      FormulaDel = addToolbarButton("del", "Remove data",
        "formulaDel", true, formulaPanel);
    }
    FormulaBox = new JComboBox();
    formulaPanel.add(FormulaBox);
    FormulaBox.setLightWeightPopupEnabled(false);
    FormulaBox.setEditable(true);
    FormulaEditor = FormulaBox.getEditor();
    FormulaListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String newItem = ((String) FormulaEditor.getItem()).trim();
        try {
          int id = 0;
          int type = BasicSSCell.UNKNOWN_SOURCE;
          boolean notify = true;

          int index1 = newItem.indexOf("d", 2);
          int index2 = newItem.indexOf(":");
          if (index1 > 0 && index2 > 0 && index1 < index2) {
            String cellName = newItem.substring(0, index1);
            BasicSSCell ssCell = BasicSSCell.getSSCellByName(cellName);
            int dataId = 0;
            try {
              dataId = Integer.parseInt(newItem.substring(index1 + 1, index2));
            }
            catch (NumberFormatException exc) {
              if (BasicSSCell.DEBUG && BasicSSCell.DEBUG_LEVEL >= 3) {
                exc.printStackTrace();
              }
            }
            if (ssCell == DisplayCells[CurX][CurY] && dataId > 0) {
              // user is 'editing' the data object
              String varName = newItem.substring(0, index2);
              String source = ssCell.getDataSource(varName);
              if (source != null) {
                ssCell.removeData(varName);
                String oldItem = null;
                for (int i=0; i 0) {
          for (int j=0; j 0) {
      for (int j=0; j= 0) {
              String sX = line.substring(eq + 1, x).trim();
              String sY = line.substring(x + 1).trim();
              try {
                sizeX = Integer.parseInt(sX);
                sizeY = Integer.parseInt(sY);
              }
              catch (NumberFormatException exc) {
                if (BasicSSCell.DEBUG) exc.printStackTrace();
              }
            }
          }

          // cell dimension
          if (keyword.equalsIgnoreCase("dimension") ||
            keyword.equalsIgnoreCase("dimensions") ||
            keyword.equalsIgnoreCase("dim"))
          {
            int x = line.indexOf("x", eq);
            if (x >= 0) {
              String sX = line.substring(eq + 1, x).trim();
              String sY = line.substring(x + 1).trim();
              try {
                dimX = Integer.parseInt(sX);
                dimY = Integer.parseInt(sY);
              }
              catch (NumberFormatException exc) {
                if (BasicSSCell.DEBUG) exc.printStackTrace();
              }
            }
          }

          // column widths
          if (keyword.equalsIgnoreCase("columns") ||
            keyword.equalsIgnoreCase("column") ||
            keyword.equalsIgnoreCase("column widths") ||
            keyword.equalsIgnoreCase("column_widths") ||
            keyword.equalsIgnoreCase("columnwidths") ||
            keyword.equalsIgnoreCase("column width") ||
            keyword.equalsIgnoreCase("column_width") ||
            keyword.equalsIgnoreCase("columnwidth"))
          {
            StringTokenizer ln = new StringTokenizer(line.substring(eq + 1));
            int nt = ln.countTokens();
            for (int z=0; z= MIN_VIS_WIDTH) colWidths.add(new Integer(cw));
            }
          }

          // rows heights
          if (keyword.equalsIgnoreCase("rows") ||
            keyword.equalsIgnoreCase("row") ||
            keyword.equalsIgnoreCase("row heights") ||
            keyword.equalsIgnoreCase("row_heights") ||
            keyword.equalsIgnoreCase("rowheights") ||
            keyword.equalsIgnoreCase("row height") ||
            keyword.equalsIgnoreCase("row_height") ||
            keyword.equalsIgnoreCase("rowheight"))
          {
            StringTokenizer ln = new StringTokenizer(line.substring(eq + 1));
            int nt = ln.countTokens();
            for (int z=0; z= MIN_VIS_WIDTH) rowHeights.add(new Integer(rh));
            }
          }

          // auto switch
          if (keyword.equalsIgnoreCase("auto switch") ||
            keyword.equalsIgnoreCase("auto_switch") ||
            keyword.equalsIgnoreCase("auto-switch") ||
            keyword.equalsIgnoreCase("autoswitch"))
          {
            String val = line.substring(eq + 1).trim();
            if (val.equalsIgnoreCase("false") ||
              val.equalsIgnoreCase("F"))
            {
              autoSwitch = false;
            }
          }

          // auto detect
          if (keyword.equalsIgnoreCase("auto detect") ||
            keyword.equalsIgnoreCase("auto_detect") ||
            keyword.equalsIgnoreCase("auto-detect") ||
            keyword.equalsIgnoreCase("autodetect"))
          {
            String val = line.substring(eq + 1).trim();
            if (val.equalsIgnoreCase("false") ||
              val.equalsIgnoreCase("F"))
            {
              autoDetect = false;
            }
          }

          // auto show
          if (keyword.equalsIgnoreCase("auto show") ||
            keyword.equalsIgnoreCase("auto_show") ||
            keyword.equalsIgnoreCase("auto-show") ||
            keyword.equalsIgnoreCase("autoshow"))
          {
            String val = line.substring(eq + 1).trim();
            if (val.equalsIgnoreCase("false") ||
              val.equalsIgnoreCase("F"))
            {
              autoShow = false;
            }
          }
        }
        gotGlobal = true;
      }
    }

    // make sure cell dimensions are valid
    if (dimX < 1 || dimY < 1) {
      displayErrorMessage("The file " + file + " has an invalid " +
        "global dimension entry", null, "VisAD SpreadSheet error");
      // reset auto-switch, auto-detect and auto-show
      setAutoSwitch(origSwitch);
      setAutoDetect(origDetect);
      setAutoShowControls(origShow);
      return;
    }

    // create label width and height arrays
    len = colWidths.size();
    int[] cw = new int[dimX];
    for (int i=0; i 0 && sizeY > 0) setSize(sizeX, sizeY);

    // reconstruct spreadsheet cells and labels
    NumVisX = dimX;
    NumVisY = dimY;
    reconstructSpreadsheet(cellNames, cw, rh, null);
    synchColRow();

    // set each cell's string
    for (int j=0; j 0) {
      // re-layout horizontal spreadsheet labels
      JPanel[] newLabels = new JPanel[NumVisX + 1];
      for (int i=0; i NumVisX - 1) selectCell(NumVisX - 1, CurY);
      reconstructHoriz(newLabels, newDrag, fcells);
      if (letter == last) LayAddCol.setEnabled(true);
      if (NumVisX == 1) LayDelCol.setEnabled(false);
    }
    return true;
  }

  /**
   * Deletes a row from the spreadsheet.
   */
  public synchronized boolean deleteRow() {
    // make sure at least one row will be left
    if (NumVisY == 1) {
      displayErrorMessage("This is the last row", null, "Cannot delete row");
      return false;
    }

    // make sure no cells are dependent on rows about to be deleted
    for (int i=0; i NumVisY - 1) selectCell(CurX, NumVisY - 1);
      reconstructVert(newLabels, newDrag, fcells);
      if (NumVisY == 1) LayDelRow.setEnabled(false);
    }
    return true;
  }

  /**
   * Resizes all cells to exactly fill the entire pane, if possible.
   */
  public void tileCells() {
    Dimension paneSize = SCPane.getSize();
    int w = paneSize.width - SCPane.getVScrollbarWidth() - 5 * NumVisX;
    int h = paneSize.height - SCPane.getHScrollbarHeight() - 5 * NumVisY;
    int wx = w / NumVisX;
    int hy = h / NumVisY;
    if (wx < MIN_VIS_WIDTH) wx = MIN_VIS_WIDTH;
    if (hy < MIN_VIS_HEIGHT) hy = MIN_VIS_HEIGHT;
    Dimension hSize = new Dimension(wx, LABEL_HEIGHT);
    Dimension vSize = new Dimension(LABEL_WIDTH, hy);
    for (int i=0; i 0) FormulaBox.removeAllItems();
        String[] varNames = DisplayCells[CurX][CurY].getVariableNames();
        int len = varNames.length;
        for (int i=0; i 0;
        CellDel.setEnabled(b);
        if (!BugFix) FormulaDel.setEnabled(b);
      }
    });
  }

  /**
   * Updates dimension checkbox menu items and toolbar buttons.
   */
  protected void refreshDisplayMenuItems() {
    Util.invoke(false, BasicSSCell.DEBUG, new Runnable() {
      public void run() {
        // update dimension check marks
        int dim = DisplayCells[CurX][CurY].getDimension();
        boolean j3d3d = (dim == BasicSSCell.JAVA3D_3D);
        boolean j2d2d = (dim == BasicSSCell.JAVA2D_2D);
        boolean j3d2d = (dim == BasicSSCell.JAVA3D_2D);
        CellDim3D3D.setState(j3d3d);
        CellDim2D2D.setState(j2d2d);
        CellDim2D3D.setState(j3d2d);
        if (!BugFix) {
          Tool3D.setEnabled(!j3d3d && CanDo3D);
          Tool2D.setEnabled(!j3d2d && CanDo3D);
          ToolJ2D.setEnabled(!j2d2d);
        }
      }
    });
  }


  // --- COLLABORATION ---

  /**
   * Determines whether or not the last remote event was from the server.
   */
  private double getColRowID() {
    TupleIface t = null;
    Real id = null;
    try {
      t = (TupleIface) RemoteColRow.getData();
      id = (Real) t.getComponent(0);
      return id.getValue();
    }
    catch (NullPointerException exc) {
      if (BasicSSCell.DEBUG) exc.printStackTrace();
    }
    catch (VisADException exc) {
      if (BasicSSCell.DEBUG) exc.printStackTrace();
    }
    catch (RemoteException exc) {
      if (BasicSSCell.DEBUG) exc.printStackTrace();
    }
    return Float.NaN;
  }

  /**
   * Gets the latest remote row and column information.
   */
  private String[][] getNewCellNames() {
    // extract new row and column information
    TupleIface t = null;
    TupleIface tc = null;
    RealTuple tr = null;
    try {
      t = (TupleIface) RemoteColRow.getData();
      tc = (TupleIface) t.getComponent(1);
      tr = (RealTuple) t.getComponent(2);
    }
    catch (NullPointerException exc) {
      if (BasicSSCell.DEBUG) exc.printStackTrace();
    }
    catch (VisADException exc) {
      if (BasicSSCell.DEBUG) exc.printStackTrace();
    }
    catch (RemoteException exc) {
      if (BasicSSCell.DEBUG) exc.printStackTrace();
    }
    if (tc == null || tr == null) return null;
    int collen = -1;
    int rowlen = -1;
    try {
      collen = tc.getDimension();
      rowlen = tr.getDimension();
    }
    catch (RemoteException exc) {
      if (BasicSSCell.DEBUG) exc.printStackTrace();
    }
    if (rowlen < 1 || collen < 1) return null;
    String[] colNames = new String[collen];
    int[] rowNames = new int[rowlen];
    String[][] cellNames = new String[collen][rowlen];
    for (int i=0; i 0) FormulaText.getCaret().moveDot(pos - 1);
      }
      else {
        // left arrow pressed; move caret left
        if (pos > 0) FormulaText.getCaret().setDot(pos - 1);
      }
    }
    else if (keyCode == KeyEvent.VK_RIGHT) {
      int pos = FormulaText.getCaretPosition();
      if (shiftHeld) {
        // shift + right arrow pressed; alter selection right
        if (pos < FormulaText.getText().length()) {
          FormulaText.getCaret().moveDot(pos + 1);
        }
      }
      else {
        // right arrow pressed; move caret right
        if (pos < FormulaText.getText().length()) {
          FormulaText.getCaret().setDot(pos + 1);
        }
      }
    }
    else if (keyCode == KeyEvent.VK_HOME) {
      if (shiftHeld) {
        // shift + home pressed; select to beginning of text
        FormulaText.getCaret().moveDot(0);
      }
      else {
        // home pressed; move to beginning of text
        FormulaText.getCaret().setDot(0);
      }
    }
    else if (keyCode == KeyEvent.VK_END) {
      if (shiftHeld) {
        // shift + end pressed; select to end of text
        FormulaText.getCaret().moveDot(FormulaText.getText().length());
      }
      else {
        // end pressed; move to end of text
        FormulaText.getCaret().setDot(FormulaText.getText().length());
      }
    }
    else commandKey = false;
  }

  /**
   * BIG HAMMER HACK.
   */
  public void keyReleased(KeyEvent e) {
    int keyCode = e.getKeyCode();
    if (keyCode == KeyEvent.VK_SHIFT) shiftHeld = false;
  }

  /**
   * BIG HAMMER HACK.
   */
  public void keyTyped(KeyEvent e) {
    char key = e.getKeyChar();
    if (!commandKey && !e.isActionKey() && key >= 32) {
      int start = FormulaText.getSelectionStart();
      int end = FormulaText.getSelectionEnd();
      String text = FormulaText.getText();
      FormulaText.setText(text.substring(0, start) +
        key + text.substring(end));
      FormulaText.getCaret().setDot(start + 1);
    }
    e.consume();
  }

  /**
   * Old x value used with cell resizing logic.
   */
  private int oldX;

  /**
   * Old y value used with cell resizing logic.
   */
  private int oldY;

  /**
   * Handles mouse presses.
   */
  public void mousePressed(MouseEvent e) {
    Component c = e.getComponent();
    for (int j=0; j= NumVisX) x = NumVisX - 1;
    if (y >= NumVisY) y = NumVisY - 1;

    // update borders of all cells
    for (int j=0; j 

Ads help maintain this website.