package nxm.sys.libg;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Scrollbar;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver;
import java.awt.image.MemoryImageSource;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import nxm.sys.inc.Keyable;
import nxm.sys.inc.MessageHandler;
import nxm.sys.inc.ProvisionalUseOnly;
import nxm.sys.lib.Args;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.Keywords;
import nxm.sys.lib.Message;
import nxm.sys.lib.Shell;
import nxm.sys.lib.StringUtil;
import nxm.sys.lib.Table;
import nxm.sys.lib.Time;
import nxm.sys.lib.XmlFile;

/** Create a gridsheet allowing spreadsheet type usage but with no on screen editing.

    NOTE: GridSheet does NOT currently support Java Serialization, even though it extends classes that support it.

    @author Beth Hassler, MSZ, Jay Hallam
 */
public class GridSheet extends MPanel implements Keyable, MessageHandler,
 ComponentListener, MouseListener, MouseMotionListener, ChangeListener {
  /** NOTE: this should be changed if the structure/fields of this class changes. */
  private static final long serialVersionUID = 2012012000311L; // for Serializable interface

  /** Enables (true) or disables (false) JC4's improved drawing code added 11-Jul-2005. */
  private static final boolean JC4_IMPROVED_DRAW = true;

  /** Enables (true) or disables (false) debugging messages for refresh calls. */
  private static final boolean DEBUG_REFRESH = false;

  /** maximum number of rows displayed           */ static int maxdisprows     = 100;
  /** maximum number of columns displayed        */ static int maxdispcols     =  75;
  /** height of scroll bar                       */ static int hscrlheight     =  10;
  /** width of scroll bar                        */ static int vscrlwidth      =  10;
  /** default block jump on vertical slide bar   */ static int vblockincrement =  10;
  /** default block jump on horizontal slide bar */ static int hblockincrement =   1;

  private static final int T_ONTYPE=1,T_VECTOR=2,T_TABLE=3;
  private static final int T_CELL=1,T_ROW=2,T_COL=3;
  private static final int T_STRING=1,T_PANEL=2,T_RASTER=3,T_LINE=4,T_TOGGLE=5,T_TIME=6;
  private static final int T_CENTER=1,T_RIGHT=2,T_LEFT=3;

  /** Must initialize to use drag-drop   */ private              boolean          allowDragAndDrop = false;
  /** Drag export source                 */ private transient    StringDragSource stringDragSource =  null;
  /** Drop import target                 */ private transient    StringDropTarget stringDropTarget =  null;
  /** Drag and Drop Data in XML format   */ public  static final int              DND_XML          =     1;
  /** Drag and Drop Data in Table format */ public  static final int              DND_TABLE        =     2;

  /** Drag export type */ private int dragExportType  = DND_XML;
  /** Drop import type */ private int dropImportType  = DND_XML;

  /** default size                             */ int rowheight             = 20;
  /** default size save as rowheight           */ int columnbuttonheight    = 20;
  /** default size - about 5 chars at 8 point  */ int rowbuttonwidth        = 39;
  /** default size - about 10 chars at 8 point */ int colbuttondefaultwidth = 79;
  /** default char size-width                  */ int charwidth             = 10;
  /** start column position                    */ int startcol              =  0;
  /** start row position                       */ int startrow              =  0;
  /** current # rows with data                 */ int currows               =  0;
  /** current # columns with data              */ int curcols               =  0;
  /** saved x/y mouse coordinates              */ int xval,yval;

  /** dragging 0-false 1-true */                  int dragging=0;
  /** draw column */                              int dragCol=-1;
  /** save position */                            int savpos = 0;
  /** row selection */                     public int selRow = -1;
  /** column selection */                  public int selColumn = -1;

  /** if grid lines are on                */ boolean gridon           =  true;
  /** if column labels are visible        */ boolean collabelson      =  true;
  /** if row labels are visible           */ boolean rowlabelson      =  true;
  /** single select mode                  */ boolean selectOne        =  true;
  /** enable special Wayne-only features? */ boolean wayneMode        = false;
  /** doubleClick support for Wayne.      */ boolean wayneDoubleClick = false;

  /** Make the ShiftKey select a RANGE of rows. */
  boolean selectRange=false;

  /** last row */
  int lastRow=-1;

  /** MultiSelect is OFF - Do not pass current and previous selection data*/
  public static final int MULTISEL_OFF=0;

  /** MultiSelect is FULL - Return current and previous selected row DATA in SELECT
      message data */
  public static final int MULTISEL_FULL=1;

  /** MultiSelect is LIST - TBD - Return list of current and previous selected rows in SELECT
      message data */
  public static final int MULTISEL_LIST=2;

  /** Multi-Selection Mode to control what data is passed back in SELECT/DESELECT messages
      when SHIFT-LMB and CTRL-LMB are
      pressed */
  public int MultiSelMsgMode = MULTISEL_OFF;

  /** delimiters for Strings     */ String delimiters =     "\t,";
  /** column label justification */ int    collabjus  = T_CENTER;

  /** command line /SCROLLEND switch state */ public boolean scrollEndSw = false;
  /** set by clear(), reset by refresh()   */        boolean scrollEnd   = false;

  /** default selection */    Color defaultsel  = new Color(0xA040FF);
  /** grid color */           Color gridcolor   = Color.darkGray;

  /** Horizontal scroll bar*/ Scrollbar horizontalScrollbar;
  /** Vertical scroll bar  */ Scrollbar verticalScrollbar;

  /** holds column formats                           */ Vector<Column> columns;
  /** holds each row                                 */ Vector<Row>    rows;
  /** holds index of which subrecord to sort data on */ int            sortIndex =     0;
  /** sort ascending or descending flag              */ boolean        sortDesc  = false;

  /** default to output format based on file type                             */           int           outtype       = T_ONTYPE;
  /** default to select by cell                                               */           int           curmode       =   T_CELL;
  /** default highlighting of selections to be done by backlighting           */           boolean       fgHilite      =    false;
  /** Draw the entire panel (grid), not just the data area                    */           boolean       full          =     true;
  /** Override full in special cases (such as when panel is in a JTabbedPane) */           boolean       needsFullDraw =    false;
  /** We draw to the image in this graphics handle most of the time           */ transient Graphics      g;
  /** and then draw to this graphics once the image is complete               */ transient Graphics      mg;
  /** This is the actual image we draw                                        */ transient BufferedImage drawimg;
  /** toggles auto selection                                                  */ public    boolean       autoSelect    =    false;
  /** template keywords                                                       */ transient Keywords      templateKeys;

  /** Choice options for refreshing          */ public static final String  refreshChoices = "Legacy,Timer";
  /** Legacy refresh choice                  */ public static final int     RM_LEGACY      =              1;
  /** Timer refresh choice                   */ public static final int     RM_TIMER       =              2;
  /** Refresh mode (set to timer by default) */ private             int     refreshMode    =       RM_TIMER;
  /** milliseconds                           */ private       final int     refreshDelay   =            300;
  /** Indicates if a refresh is needed.      */ private             boolean needRefresh    =          false;
  /** Indicates if currently doing a refresh */ private             boolean inRefresh      =          false;

  /** Timer used for the refreshes              */ private           Timer          refreshTimer;
  /** Called by timer when it's time to refresh */ private transient ActionListener refreshListener;

  //*********************************************************************************************/
  /** No argument constructor */
  public GridSheet() {
    super();
    refreshListener = new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        if (needRefresh) {
          forceRefresh();
        }
      }
    };
    // Proceed if not headless (Since NeXtMidas 3.5.0 - BUG 2817)
    // and not running from a jar file (Since NeXtMidas 4.1.2 - NXM-3864)
    if (!Shell.isHeadless() && !Shell.isRunningFromJarFile()) {
      refreshTimer =  new Timer(refreshDelay, refreshListener);
      refreshTimer.start();
    }
  }

  /** Enable or disable the drag and drop.  The first time drag and drop is enabled the source
      and target are initialized (this only has to be done once).
    @param in Allow drag and drop if true, disallow if false
  */
  public void setDragAndDrop(boolean in) {
    if (in) {
      allowDragAndDrop = true;
      if (stringDragSource == null) {
        // get the appropriate method to use when Dragging
        String meth = (dragExportType==DND_XML) ? "getSelectedData" : "getSelectedRowsTable";
        stringDragSource = new StringDragSource(this,meth,new Class<?>[]{});
      }
      if (stringDropTarget == null) {
        stringDropTarget = new StringDropTarget(this,"dropData",new Class<?>[]{String.class});
      }
    } else {
      allowDragAndDrop = false;
    }
  }

  /** Enable or disable the drag and drop.  The first time drag and drop is enabled the source
      and target are initialized (this only has to be done once).
      @param type "XML" or "TABLE", or "true" to use defaults  or "false" to disable
      @since NeXtMidas 3.3.0
   */
  public void setDragAndDrop (String type) {
    if (Args.falseStates.contains(type)) {
      setDragAndDrop(false);
    } else if (Args.trueStates.contains(type)) {
      setDragAndDrop(true);
    } else {
      setDragDataType(type);
      setDropDataType(type);
      setDragAndDrop(true);
    }
  }

  /** Is the drag and drop feature enabled?
   *  @return true if drag and drop is enabled */
  public boolean isDragAndDropEnabled() {
    return allowDragAndDrop;
  }

  /** Set the type of Data format to use when dragging
      @param type "XML" or "TABLE"
      @since NeXtMidas 3.3.0
   */
  public void setDragDataType (String type) {
    if      (type.equalsIgnoreCase("XML"))   dragExportType = DND_XML;
    else if (type.equalsIgnoreCase("TABLE")) dragExportType = DND_TABLE;
  }

  /** Get the type of Data format to use when dragging
      @return "XML" or "TABLE"
      @since NeXtMidas 3.3.0
   */
  public String getDragDataType () {
    if (dragExportType==DND_TABLE) return "TABLE";
    else return "XML";
  }

  /** Set the type of Data format to use when Dropping
      @param type "XML" or "TABLE"
      @since NeXtMidas 3.3.0
   */
  public void setDropDataType (String type) {
    if      (type.equalsIgnoreCase("XML"))   dropImportType = DND_XML;
    else if (type.equalsIgnoreCase("TABLE")) dropImportType = DND_TABLE;
  }

  /** Get the type of Data format to use when dragging
      @return "XML" or "TABLE"
      @since NeXtMidas 3.3.0
   */
  public String getDropDataType () {
    if (dragExportType==DND_TABLE) return "TABLE";
    else return "XML";
  }

  /** Convert the dropped data into a table and process it
      As of NeXtMIDas 3.3.0, this dropped data will look at {@link #dropImportType}
      to determine to treat the dropped data as XML or a Table
      @param inputdata data to be converted into a table and processed
   */
  public void dropData(String inputdata) {
    try {
      Table t = new Table();
      if (inputdata==null || inputdata.length()<7) return;
      if (dropImportType==DND_XML) {
        XmlFile.fromXML(inputdata,t); // Moved fromXML() to XmlFile from nxm.sys.prim.convert. since 3.9.2 TODO: see if we can use XmlFile.fastXmlToTable(..) instead to de-couple sys.libg from sys.prim package
      }
      else t = new Table(inputdata);
      MW.process("DROPDATA",0,t);
    } catch(Exception e) { System.err.println("Exception in dropData;"+e); e.printStackTrace(); }
  }

  /** get selected Data
   *  @return String with selected data
   */
  public String getSelectedData() { return getSelectedRows(MW.name); }

  /** Turns on/off special Wayne-only features. Only Wayne is allowed to use this.
   *  @param wayne true if in "Wayne" mode */
  public void setWayneMode(boolean wayne) {
    wayneMode = wayne;
  }

  /** add this to the MWindow
   *
   *  @param mw MWindow
   */
  public void addTo(MWindow mw) {
    try {
      initHierarchyListener();
      MW = mw;
      defaultsel  = MW.theme.cwfh.darker();
      gridcolor   = MW.theme.cfg.darker().darker();
      if (MW.panel != null) { // Since NeXtMidas 3.5.0. Skip if in Headless state. BUG 2817
        MW.panel.add(this,0);
      }
      mg = this.getGraphics();
      if (mg != null){ // Since NeXtMidas 3.5.0. Skip if in Headless state. BUG 2817
        int width  = Math.max(1,MW.pos.w); // Min of 1 pixel
        int height = Math.max(1,MW.pos.h); // Min of 1 pixel
        drawimg = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(width, height);
        //drawimg = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        g = GraphicsEnvironment.getLocalGraphicsEnvironment().createGraphics(drawimg);
        //g = drawimg.getGraphics();
        g.setFont(new Font("Monospaced",Font.PLAIN,12)); //else the calculations are off
        addComponentListener(this);
        addMouseListener(this);
        if (!allowDragAndDrop) addMouseMotionListener(this);
        addMouseListener(MW);
        addKeyListener(MW);
        init();
      }
    } catch(Exception e) { System.out.println("Exception in addTo;"+e); e.printStackTrace(); }
  }

  /** Initializes the HierarchyListener which will trap changes in the AWT hierarchy (this
      includes the window changed due to PUSH/POP messages.
   */
  private void initHierarchyListener() {
    addHierarchyListener(new java.awt.event.HierarchyListener() {
      public void hierarchyChanged(HierarchyEvent evt) {
        mg = getGraphics();
      }
    });
  }

  /** close */
  public void close() {
    removeComponentListener(this);
    removeMouseListener(this);
    if (!allowDragAndDrop) removeMouseMotionListener(this);
    removeMouseListener(MW);
    removeKeyListener(MW);
    if (g!=null) g.dispose();
    if (MW.panel!=null) MW.panel.remove(this); // Since NeXtMidas 3.5.0 Proceed only if not headless BUG 2817
    if (refreshTimer != null) {
      refreshTimer.removeActionListener(refreshListener);
      refreshTimer.stop();
    }
  }


  /** Overrides MPanel event handlers of interest.  */
  public void componentResized (ComponentEvent e) {
    if (MW == null) return;
    try {
      //setSize(MW.pos.w,MW.pos.h);
      if (horizontalScrollbar != null && verticalScrollbar != null) {
        int hscroll = hscrlheight;
        if (!horizontalScrollbar.isVisible()) hscroll=0;
        int numVisRows;
        if (MW.pos.h-columnbuttonheight-hscroll < maxdisprows*rowheight+columnbuttonheight)  {
          numVisRows = (MW.pos.h-columnbuttonheight-hscroll)/rowheight ;
        }
        else  { numVisRows = maxdisprows; }
        verticalScrollbar.setValues(startrow,numVisRows,0,currows);
      }
    } catch(Exception ee) { System.out.println("Exception in processEvent;"+ee); ee.printStackTrace(); }
  }
  public void componentShown (ComponentEvent e) {  }
  public void componentHidden (ComponentEvent e) { }
  public void componentMoved (ComponentEvent e) { }

  public void mouseClicked (MouseEvent e) {
    int mods = ((InputEvent)e).getModifiersEx(); // Updated in 3.9.0 to extended modifiers
    if (wayneMode) { wayneDoubleClick = (e.getClickCount() == 2); }
    if (SwingUtilities.isLeftMouseButton(e)) { // Updated in 3.9.0 to use SwingUtilites to find out the mouse button
      if ((mods&MouseEvent.SHIFT_DOWN_MASK) !=0 ) { // Updated in 3.9.0 to use extended modifiers
        //  SHIFT for contiguous multiple selection
        selectOne   = false;
        selectRange = true;
      }else if ((mods&MouseEvent.CTRL_DOWN_MASK) !=0 ){ // Updated in 3.9.0 to use extended modifiers
        // CTRL is used for non-contiguous multiple selection
        selectOne   = false;
        selectRange = false;
      }else{
        selectOne   = true;
        selectRange = false;
      }
      xval=e.getX();
      yval=e.getY();
      processMouse1Event(xval, yval);
      this.requestFocus();
      qrefresh();
    }
  }
  public void mouseDragged (MouseEvent e) {
    if (SwingUtilities.isRightMouseButton(e)) { // Updated in 3.9.0 to use SwingUtilites to find out the mouse button
      dragging = 1;
      this.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
    }
  }
  public void mousePressed (MouseEvent e) {
    if (SwingUtilities.isRightMouseButton(e)) { // Updated in 3.9.0 to use SwingUtilites to find out the mouse button
      xval=e.getX();
      yval=e.getY();
//      int selRow = (yval-columnbuttonheight+(startrow*rowheight))/rowheight;
      try {
        int colCount = columns.size();
        int strtpos = (rowlabelson) ? rowbuttonwidth : 0;
        for (int i=startcol, colpos=strtpos; i<colCount; i++) {
          Column gc = columns.elementAt(i);
          if (gc.visible) {
            if (xval>=colpos && xval<=colpos+gc.width) { savpos=colpos; dragCol = i;}
            colpos += gc.width;
          }
        }
      } catch (ArrayIndexOutOfBoundsException aie) {
      //System.out.println("out of bounds: "+selRow);
      }
    }
  }
  public void mouseReleased (MouseEvent e) {
    this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    if (dragging == 1 && dragCol != -1) {
      xval=e.getX();
      Column gc = columns.elementAt(dragCol);
      int newWidth = xval-savpos;
      if (newWidth > gc.origWidth)
      gc.width = xval - savpos;
      else gc.width = gc.origWidth;
      if (gc.cellType == T_PANEL) adjustColPanelWidth(gc.origIndex,gc.width);
      qrefresh();
      dragging = 0;
      dragCol  = -1;
    }
  }
  public void mouseMoved (MouseEvent e) {
    xval=e.getX();
    yval=e.getY();
    processMouseMove(xval, yval);
  }
  public void mouseEntered (MouseEvent e) { }
  public void mouseExited (MouseEvent e) { }

  /**
   * Initialize.  Generate the buttons and scrollbars.
   */
  public void init() {
    try {
      setLayout(null);
      setSize(MW.pos.w,MW.pos.h);
      setBackground(MW.theme.cbg);
      setFont(new Font("Monospaced",Font.PLAIN,12));

      columns = new Vector<>();
      rows = new Vector<>();

      horizontalScrollbar = new Scrollbar(Scrollbar.HORIZONTAL);
      horizontalScrollbar.setBounds(0,MW.pos.h-hscrlheight,MW.pos.w-vscrlwidth,hscrlheight);
      horizontalScrollbar.setBlockIncrement(hblockincrement);
      horizontalScrollbar.addAdjustmentListener(new HScrollAdjustmentListener());
      horizontalScrollbar.setValues(0,0,0,0);

      verticalScrollbar = new Scrollbar(Scrollbar.VERTICAL);
      verticalScrollbar.setBounds(MW.pos.w-vscrlwidth,0,vscrlwidth,MW.pos.h-hscrlheight);
      verticalScrollbar.addAdjustmentListener(new VScrollAdjustmentListener());
      verticalScrollbar.setValues(0,0,0,0);

      add(horizontalScrollbar);
      add(verticalScrollbar);

      GridComponentAdapter gridca = new GridComponentAdapter();
      this.addComponentListener(gridca);

      // the code below checks for the special case of the List being in a TabbedPane,
      // which requires some special drawing when switching tabs (BUG 2229)
      Component child  = this;
      Component parent = getParent();
      while (parent != null) {
        if (parent instanceof JTabbedPane) {
          ((JTabbedPane)parent).addChangeListener(this);
        }
        child = parent;
        parent = child.getParent();
      }

      qrefresh();
    } catch(Exception e) { System.out.println("Exception in init;"+e); e.printStackTrace(); }
  }

  public void update(Graphics g) { paint(g); }

  /** Overrides MPanel paint.   */
  public void paint(Graphics g) {
    try {
      Rectangle rect = g.getClip().getBounds();
      if (rect.width<=1 || rect.height<=1) return;
      if (MW == null) return;
      full=false;
      MW.process ("REFRESH",0,rect);
    } catch(Exception e) { System.out.println("Exception in paint;"+e); e.printStackTrace(); }
  }

  /** refresh */
  public void qrefresh() {
    full=true;
    MW.process ("REFRESH",0,null);
  }

  /** refresh
   *
   *  @param refreshGraphics true if should create new Graphics (dispose of old)
   */
  public void refresh(boolean refreshGraphics) {
    if (refreshGraphics) {
      if (g!=null) { g.dispose(); }
      mg = this.getGraphics();
      int width  = Math.max(1,MW.pos.w); // Min of 1 pixel
      int height = Math.max(1,MW.pos.h); // Min of 1 pixel
      drawimg = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
      if (!Shell.isHeadless()) { // Since NeXtMidas 3.5.0 Proceed only if not headless BUG 2817
        g = drawimg.getGraphics(); //used to reset to g = this.getGraphics()
      }
    }
    refresh();
  }

  /** Set the refresh mode
   *  @param in_mode the refresh mode */
  public void setRefreshMode(int in_mode) {
    refreshMode = in_mode;
  }

  /** Gets the number of visible rows.
      @return The number of visible rows.
      @since NeXtMidas 2.1.0
   */
  private int getNumVisRows() {
    return (MW.pos.h-columnbuttonheight-hscrlheight)/rowheight;
  }

  /** In LEGACY refresh mode a refresh is forced.  In TIMER refresh mode just sets a
  flags indicating the need for a refresh and one happens on the next timer interval.
  */
  public void refresh() {
    if (refreshMode == RM_TIMER) {
      needRefresh = true;
    } else {
      forceRefresh();
    }
  }

  @Override
  public void print(Graphics pg) { // since 3.4.2 for BUG 2802
    // forceRefresh(pg);
    super.print(pg);
    if (pg != null && drawimg != null) {
      pg.drawImage(drawimg, 0, 0, this);
    }
  }

  /** Force a refresh */
  public void forceRefresh() {
    if(DEBUG_REFRESH) { System.out.println("forceRefresh()"); }
    if(JC4_IMPROVED_DRAW) {
      if(inRefresh) return;
      inRefresh = true;
    }

    needRefresh = false;
    try {
      int width  = Math.max(1,MW.pos.w); // Min of 1 pixel
      int height = Math.max(1,MW.pos.h); // Min of 1 pixel
      //drawimg = createImage(width, height);
      drawimg = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
      if (!Shell.isHeadless()) { // Since NeXtMidas 3.5.0 Proceed only if not headless BUG 2817
        g = drawimg.getGraphics();
        g.setFont(new Font("Monospaced",Font.PLAIN,12)); //else looks bad for font
      }
      int mode;
      Row arow;
      int rowbuttonwidth;

      if (g != null) { // Since NeXtMidas 3.5.0 Proceed only if not null BUG 2817
        FontMetrics fm = g.getFontMetrics();
        charwidth = fm.charWidth('M');
        rowheight = fm.getHeight() + 2;

        g.setColor(MW.theme.cbg);
        if (full||needsFullDraw) g.fillRect(0,0,MW.pos.w,MW.pos.h);
        needsFullDraw = false;
        //draw grid lines and column buttons
        g.setColor(gridcolor);
      }
      if ((horizontalScrollbar == null) || (verticalScrollbar == null)) return;
      int hscroll = hscrlheight;
      if (!horizontalScrollbar.isVisible()) hscroll=0;

      int curcolsize=0;
      int sindex = -1;
      for (int i=0; i<curcols; i++) {
          try {
            Column gc = columns.elementAt(i);
            if (gc.visible) curcolsize+=gc.width;
            if (gc.stretch) sindex = i;
          } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("out of bounds, i: "+i);
          }
      }
      if (sindex != -1) curcolsize = stretchColumn(sindex, curcolsize);

      if (rowlabelson) rowbuttonwidth = this.rowbuttonwidth; else rowbuttonwidth=0;
      int colpos=rowbuttonwidth;
      int colIndex=startcol;
      int numVisCols = 0;
      int realWidth = MW.pos.w-rowbuttonwidth;
      for (int i=0, tmpsize=0, savtmp=0;i<maxdispcols && colIndex<=curcols;i++) {
          try {
            Column gc = columns.elementAt(colIndex);
            if (gc.visible) {
              int colsize=gc.width;
              if (colsize<0) colsize=0;
              tmpsize+=gc.width;
              if (tmpsize!=savtmp && tmpsize<realWidth) {
                numVisCols++;
                savtmp = tmpsize;
              }
              colpos+=colsize;
              if (gridon && colsize>0) g.drawLine(colpos-1,columnbuttonheight,colpos-1,MW.pos.h); //vertical grid lines
            }
            colIndex++;
          } catch (ArrayIndexOutOfBoundsException e) {
          }
      }

      if (curcolsize<MW.pos.w) {
          horizontalScrollbar.setVisible(false);
          startcol=0;
          verticalScrollbar.setBounds(MW.pos.w-vscrlwidth,0,vscrlwidth,MW.pos.h);
      } else {
          horizontalScrollbar.setVisible(true);
          horizontalScrollbar.setValues(startcol, numVisCols, 0, curcols);
          verticalScrollbar.setBounds(MW.pos.w-vscrlwidth,0,vscrlwidth,MW.pos.h-hscrlheight);
      }

      //draw horizontal grid lines and row buttons
      int vscroll = vscrlwidth;
      if (!verticalScrollbar.isVisible()) vscroll=0;
      int rowpos = columnbuttonheight-2;
      for (int j=0;j<maxdisprows;j++) {
          if (gridon) {
            g.setColor(gridcolor);
            g.drawLine(rowbuttonwidth,rowpos,MW.pos.w,rowpos); //horizontal line
          }
          rowpos+=rowheight;
      }

      if ((currows)*rowheight+columnbuttonheight+hscroll<=MW.pos.h) {
          verticalScrollbar.setVisible(false);
          startrow=0;
          horizontalScrollbar.setBounds(0,MW.pos.h-hscrlheight,MW.pos.w,hscrlheight);
      } else {
          verticalScrollbar.setVisible(true);
          horizontalScrollbar.setBounds(0,MW.pos.h-hscrlheight,MW.pos.w-vscrlwidth,hscrlheight);

          if(scrollEnd) {
            int numVisRows = (MW.pos.h-columnbuttonheight-hscroll)/rowheight;
            startrow = currows-numVisRows;
            verticalScrollbar.setValues(startrow,numVisRows,0,currows);

            // scrollEnd boolean cleared after initial refresh. If not,
            //   scrollbar actions would be overrided by the scrollEnd
            //   boolean and you would not be able to scroll off of the
            //   bottom.
            scrollEnd=false;
          }
      }

      // Draw the column "buttons"
      int endcol = Math.min(startcol+maxdispcols,curcols);
      colpos=rowbuttonwidth;
      for (int j=startcol;j<endcol;j++) {
        Column gc = columns.elementAt(j);
        if (gc.visible) {
          if (gc.selected) {
            g.setColor(defaultsel);
            mode = -1;
          } else {
            g.setColor(MW.theme.cwfg);
            mode = 0;
          }
          shadowbox(colpos,0,gc.width,columnbuttonheight,mode);
          int pos = (gc.width-(gc.label.length()*charwidth))/2; //default T_CENTER
          if (collabjus == T_LEFT)
            pos = 1; //charwidth;
          else if (collabjus == T_RIGHT)
            pos = gc.width-(gc.label.length()*charwidth)-1; //charwidth
          g.drawString(gc.label,colpos+pos,columnbuttonheight-5);
          colpos+=gc.width;
        }
      }

      int endrow;

      if(JC4_IMPROVED_DRAW) {
        // JC4: Don't draw rows that are off the bottom of the screen.
        //      Computation of numVisRows stolen from elsewhere in this class.
        //
        //      numVisRowsGap = Fudge-factor to account for numVisRows being off by 1.5 rows
        //                      (on average).
        //
        //int endrow = Math.min(startrow+maxdisprows,currows);
        final int numVisRowsGap = 2;

        endrow = Math.min(startrow+getNumVisRows()+numVisRowsGap,currows);

      } else {
        endrow = Math.min(startrow+maxdisprows,currows);
      }
      rowpos = (columnbuttonheight+rowheight)-2;


      // For columns with panels, we must hide the rows that are not in view
      for (int i=0; i<startrow; i++) {
            arow = null;
            arow = rows.elementAt(i);
            for (int j=startcol;j<endcol;j++) {
                Column gc = columns.elementAt(j);
                if (gc.visible) {
                  int jj = gc.origIndex;
                  if (arow.cells[jj] instanceof CellPanel || arow.cells[jj] instanceof CellRaster) arow.cells[jj].hidePanel();
                }
            }
      }

      // Draw the row buttons and data
//jph -- 23mar05        for (int i=startrow,rowIdx=0;i<endrow;i++,rowIdx++) {
      for (int i=startrow,rowIdx=0;i<endrow && i < rows.size();i++,rowIdx++) {
            if(DEBUG_REFRESH) {
              System.out.println("GS: drawing row " + i
                               + "  (startrow="+startrow+" endrow="+endrow+")");
            }
            arow = null;
            try {
              arow = rows.elementAt(i);
            } catch (ArrayIndexOutOfBoundsException e) {
              System.out.println("out of bounds, i: "+i);
              continue;
            }

            // Draw the row "button"
            if (arow.selected) {
              g.setColor(defaultsel);
              mode = -1;
            } else {
              g.setColor(MW.theme.cwfg);
              mode = 0;
            }
            shadowbox(0,rowIdx*rowheight+columnbuttonheight,rowbuttonwidth-1,rowheight,mode);
//MSZ Change to catch exceptions
            int pos=0;
            try {
              pos = (rowbuttonwidth-(arow.label.length()*charwidth))/2;
            } catch (Exception e) {
              System.out.println("GS: arow;"+arow+","+arow.label+" ???");
            }
            if (arow.label!=null) g.drawString(arow.label,pos,(rowIdx+1)*rowheight+columnbuttonheight-4);
//              int pos = (rowbuttonwidth-(arow.label.length()*charwidth))/2;
//              g.drawString(arow.label,pos,(rowIdx+1)*rowheight+columnbuttonheight-4);
//MSZ Change DONE

            colpos=rowbuttonwidth;
            for (int j=0; j<startcol; j++) {
                Column gc = null;
                try {
                  gc = columns.elementAt(j);
                } catch (ArrayIndexOutOfBoundsException e) {
                  System.out.println("out of bounds, j: "+j);
                }
                if (gc.visible) {
                  int jj = gc.origIndex;
                  if (arow.cells[jj] instanceof CellPanel || arow.cells[jj] instanceof CellRaster) arow.cells[jj].hidePanel();
                }
            }

            // Fill in the data
            for (int j=startcol;j<endcol;j++) {
                Column gc = null;
                try {
                  gc = columns.elementAt(j);
                } catch (ArrayIndexOutOfBoundsException e) {
                  System.out.println("out of bounds, j: "+j);
                }
                int jj = gc.origIndex;
                if (gc.visible) {
                  try {
                    arow.cells[jj].showPanel();
                    arow.cells[jj].setRectangle(colpos,rowpos,gc.width,rowheight);
                    arow.cells[jj].setMaxX(colpos + gc.width-1);

                    if (arow.selected || gc.selected || arow.cells[j].isSelected())
                      arow.cells[jj].setSelected(fgHilite, defaultsel);
                      //arow.cells[jj].setSelected(fgHilite, defaultsel, MW.theme.cfg, MW.theme.cbg);
                    else {
                      int rs = arow.rowData.size(); //BAH Nov03
                      arow.cells[jj].setColors(gc.getColorRule().getColor(arow.rowData.subList(0,rs)), arow.cells[jj].getDefBg()); //Nov03
                      //arow.cells[jj].setColors(arow.cells[jj].getDefFg(),arow.cells[jj].getDefBg());
                      //arow.cells[jj].setColors(MW.theme.cfg,MW.theme.cbg);
                    }
                    arow.cells[jj].refresh();

                  } catch (ArrayIndexOutOfBoundsException e) { }
                  colpos+=gc.width;
                } else {
                  try {
                    if (arow.cells[jj] instanceof CellPanel || arow.cells[jj] instanceof CellRaster) arow.cells[jj].hidePanel();
                  } catch (ArrayIndexOutOfBoundsException e) { }
                }
            }
            rowpos+=rowheight;
      }

      //Cover up the unused areas
      if (collabelson && rowlabelson) shadowbox(0,0,rowbuttonwidth,columnbuttonheight,0);
      if (vscroll>0 && hscroll>0 ) shadowbox(MW.pos.w-vscrlwidth,MW.pos.h-hscrlheight,hscrlheight,vscrlwidth,0);
      if (mg!=null) mg.drawImage(drawimg,0,0,this);
    } catch(Exception e) {
      if (e instanceof ArrayIndexOutOfBoundsException) {
        Shell.writeln("Out of Bounds "+e);
      } else {
        Shell.printStackTrace("Exception in GridSheet.refresh(): "+e, e);
      }
    }
    if(JC4_IMPROVED_DRAW) {
      inRefresh   = false;
      needRefresh = false;
    }
  }

  /** process a message from parent process or config menu
   *
   *  @param msg    message
   *  @return status of 1
   */
  public int processMessage (Message msg) { return processMessage (msg,null,null); }

  /** process a message
   *
   *  @param msg    message
   *  @param msgid  message id
   *  @param parent parent
   *  @return status of 1
   */
  public int processMessage (Message msg, Object msgid, Object parent) {
    int stat = 1;
    boolean done=false;

    for (int i=0; i<curcols; i++) {
        try {
          Column gc = columns.elementAt(i);
          if (gc.label.equalsIgnoreCase(msg.name)) {
            Table t = new Table();
            t.put("TYPE","CELL");
            t.put("ROW", Integer.valueOf(gc.selRow));
            t.put("COLUMN", gc.subr);
            t.put("NAME",gc.subr);
            EnumColorRule erule = (EnumColorRule)gc.getColorRule();
            String mitem = Convert.o2s(msg.data);
            t.put("ITEM",mitem);
            if (selectOne) t.put("SELECTONE",1); //MSZ Added for selection handling in macro
            else t.put("SELECTONE",0);
            if (selectRange) t.put("SELECTRANGE",1);
            else t.put("SELECTRANGE",0); //MSZ Added Done
            for (Table.Iterator ti=erule.dataTable.iterator(); ti.getNext() && !done; ) {
              String dKey = ti.key;
              String dVal = Convert.o2s(ti.value).toUpperCase();
              if (dVal.equalsIgnoreCase(mitem)) { t.put("VALUE",dKey); done=true;}
            }
            setCell(t);
            MW.process("SELECT",0,t);
          }
        } catch(Exception e) { System.out.println("Exception in processMessage;"+e); e.printStackTrace();}
    }
    return stat;
  }

  /**
   * Resize the individual panels in a column to the new column width
   */
  private void adjustColPanelWidth(int colIndex, int newWidth) {
    try {
      for (int i=0; i<currows; i++) {
        try {
          Row arow = rows.elementAt(i);
          Component[] c = ((CellPanel)arow.cells[colIndex]).cellPanel.getComponents();
          for (int j=0; j<c.length; j++) c[j].setSize(newWidth,rowheight);
        } catch (ArrayIndexOutOfBoundsException aie) {
          System.out.println("out of bounds, i: "+i);
        }
      }
    } catch(Exception e) { System.out.println("Exception in adjustColPanelWidth;"+e); e.printStackTrace(); }
  }

  private void setColLabels(String[] plabels, int labelcount, boolean repaint) {
    try {
      for (int i=0;i<labelcount;i++) {
        Column gc = new Column();
        gc.subr=plabels[i].trim();
        gc.label=plabels[i].trim();
        gc.width=(plabels[i].length()+2)*charwidth;
        gc.index=Integer.valueOf(i);
        gc.origIndex=i;
        gc.cellType=T_STRING;
        gc.setColorRule(new ConstColorRule(MW.theme.cfg));
        if (columns != null) { // Since NeXtMidas 3.5.0 Proceed only if not null BUG 2817
          columns.addElement(gc);
        }
      }
      curcols=labelcount;
//      int endcol = Math.min(startcol+maxdispcols,curcols);
      if (repaint) qrefresh();

    } catch (Exception e) { System.out.println("Exception in setColLabels:"+e); e.printStackTrace(); }
  }

  private void addRows(Vector<? super Object> values, int rowSize, boolean repaint, int rowPos) {
    try {
      for (int i=0, index=0; i<values.size(); i+=rowSize) {
        Row arow=new Row(rowSize);
        index=i;
        for (int j=0,k=0; j<rowSize; j++) {
          Column gc = columns.elementAt(j);
          k = gc.origIndex;
          if (gc.definedColor == null) gc.definedColor = MW.theme.cfg;

          if (gc.cellType == T_PANEL)
            arow.cells[k]=(CellRenderer)new CellPanel(MW, this, MW.theme.cbg, MW.theme.cfg);
          else if (gc.cellType == T_TOGGLE)
            arow.cells[k]=(CellRenderer)new CellToggle(MW.theme.cbg, MW.theme.cfg);
          else if (gc.cellType == T_RASTER) {
            arow.cells[k]=(CellRenderer)new CellRaster(MW, gc.rasterFrame, gc.rasterLevels,
                                                       gc.rasterColorMap, gc.rasterSelColor);
          } else if (gc.cellType == T_TIME) {
            arow.cells[k]=(CellRenderer)new
            CellString(gc.getColorRule().getColor(values.subList(index,index+rowSize)), MW.theme.cbg, gc.timeFormat);
          } else {
            arow.cells[k]=(CellRenderer)new
            CellString(gc.getColorRule().getColor(values.subList(index,index+rowSize)), MW.theme.cbg);
            //arow.cells[j]=(CellRenderer)new CellString(MW.theme.cfg, MW.theme.cbg);
          }
                  //Treat value as a String unless it has a Number Formatter or is DMS
          if (values.elementAt(k+index) instanceof String && (gc.nf == null) && (!gc.dms)) {
            if (gc.getColorRule() instanceof EnumColorRule)  {
              if (values.elementAt(k+index).toString().length()>0) { //BAH Oct03
                arow.cells[k].setData(gc.getColorRule().getData(values.elementAt(k+index).toString()).trim());
                arow.cells[k].setColors(gc.getColorRule().getColor(values.subList(index,index+rowSize)), arow.cells[k].getDefBg());
              } else
                arow.cells[k].setData("ERROR");                      //BAH Oct03
              }
                else {
                  arow.cells[k].setData(values.elementAt(k+index).toString().trim());
                  arow.cells[k].setColors(gc.getColorRule().getColor(values.subList(index,index+rowSize)), arow.cells[k].getDefBg());
                }

                arow.rowData.addElement(arow.cells[k].getData());
          } else {
            // Treat every non-string value as a Double --- for sorting purposes
            if (gc.getColorRule() instanceof EnumColorRule)  {
              String s = gc.getColorRule().getData(values.elementAt(k+index).toString());
              values.setElementAt(s,k+index);
            } else  {
              //jph -- start -- DR 266, g542564-1 -- Exception on empty string
              //jph Double d = Double.valueOf(values.elementAt(k+index).toString());
              //jph values.setElementAt(d,k+index);
              String str = values.elementAt(k+index).toString();
              if (str.length() > 0) {
                Double d = Double.valueOf(str);
                values.setElementAt(d,k+index);
              } else {
                values.setElementAt(str,k+index);
              }
                //jph -- end -- DR 266, g542564-1 -- Exception on empty string
            }
            String data = "";
            Object aValue = values.elementAt(k+index);
            //only need to check for Double because all data is either a String, or has been widened to a Double
            //in if-else code block just above
            if (aValue instanceof Double) data = gc.format( ((Double)aValue).doubleValue() );
            else data = values.elementAt(k+index).toString().trim();
            arow.cells[k].setData(data);
            arow.cells[k].setColors(gc.getColorRule().getColor(values.subList(index,index+rowSize)),arow.cells[k].getDefBg());
            arow.rowData.addElement(values.elementAt(k+index));
          }

          if (gc.cellType == T_RASTER) arow.rowData.setElementAt(arow.cells[k].getDataObj(), k+index);

          if (gc.width==colbuttondefaultwidth) gc.width=(arow.cells[k].getData().length()+2)*charwidth;

          if ((arow.cells[k].getData().length()+2)*charwidth > gc.width)
            gc.width=(arow.cells[k].getData().length()+2)*charwidth;

          if (gc.label.length() == 0) gc.label = "F"+(j+1);
          if ((gc.label.length()+1)*charwidth > gc.width) gc.width=(gc.label.length()+1)*charwidth;
          if (gc.origWidth == 0) gc.origWidth = gc.width;
          columns.setElementAt(gc,j);
        }

        if (rowPos>0) {
          rowPos--;  // Row is 1-based but Vector is 0-based
          if (rowPos>rows.size()) rowPos=rows.size();
          rows.insertElementAt(arow,rowPos);
        } else rows.addElement(arow);
        currows = rows.size();
        if (rowPos>0) {renumberRows(false);} else {arow.label = ""+currows;}
        rowbuttonwidth = Math.max(rowbuttonwidth,(arow.label.length()+1)*charwidth);
        if (rowSize>curcols) curcols=rowSize;
        int hscroll = hscrlheight;
        if (!horizontalScrollbar.isVisible()) hscroll=0;
        int numVisRows;
        if (MW.pos.h-columnbuttonheight-hscroll < maxdisprows*rowheight+columnbuttonheight)
          numVisRows = (MW.pos.h-columnbuttonheight-hscroll)/rowheight ;
        else numVisRows = maxdisprows;
        verticalScrollbar.setValues(0,numVisRows,0,currows);
        if (repaint) qrefresh();
      }
    } catch (Exception e) { System.out.println("Exception in addRow:"+e); e.printStackTrace(); }
  }

//jph -- I THINK THIS CAN BE DELETED
  private void pollRows(Vector<?> values, int rowSize, boolean repaint, int row){
    try {
      for (int i=0, index=0; i<values.size(); i+=rowSize) {
        Row arow=rows.elementAt(row);
        index=i;
        for (int j=0,k=0; j<rowSize; j++) {
          Column gc = columns.elementAt(j);
          k = gc.origIndex;
          if (gc.getColorRule() instanceof EnumColorRule)  {
            arow.cells[k].setData(gc.getColorRule().getData(values.elementAt(k+index).toString()));
          }
          else  {
            arow.cells[k].setData(values.elementAt(k+index).toString());
          }
          arow.cells[k].refresh();
        }
        rows.set(row,arow);
      }
    } catch (Exception e) { System.out.println("Exception in pollRows:"+e); e.printStackTrace(); }
  }

  /**
   * Stretch the indicated column to fill the display.
   * Currently only stretches one column, and it will be
   * the last one indicated in the template.
   */
  private int stretchColumn(int sindex, int currTotalWidth) {
    try {
      int colsize = currTotalWidth;
      int vscroll = (!verticalScrollbar.isVisible()) ? 0 : vscrlwidth;
      int rbw = (rowlabelson) ? rowbuttonwidth : 0;

      Column gc = columns.elementAt(sindex);
      if (colsize>MW.pos.w) {
        colsize -= gc.width;
        colsize += gc.origWidth;
        gc.width = gc.origWidth;
      }
      if (colsize<MW.pos.w-1) {
        int diff = MW.pos.w - colsize - rbw - vscroll;
        colsize -= gc.width;
        gc.width = gc.width + diff - 1;
        colsize += gc.width;
      }
      return colsize;
    } catch (Exception e) { System.out.println("Exception in stretchColumn:"+e); e.printStackTrace(); return currTotalWidth; }
  }

  private void processMouseMove(int xval, int yval) {
    try {
      Column gc;
      String tmp=null;
      if ((xval<=0) && (yval<=0)) return;
      int selRow = (yval-columnbuttonheight+(startrow*rowheight))/rowheight;
      if (selRow>currows) return;

      try {
        if (rowlabelson && (xval<rowbuttonwidth-1)) {
          //Do nothing
        } else {
          Row arow = rows.elementAt(selRow);
          for (int i=startcol, colpos=rowbuttonwidth; i<arow.count; i++) {
            if (collabelson && (yval<columnbuttonheight)) {
              //Do nothing
            } else {
              gc = columns.elementAt(i);
              int oi = gc.origIndex;
              if (arow.cells[oi].inXBounds(xval)) {
                if (gc.cellType == T_RASTER) {
                  boolean found = ((CellRaster)arow.cells[oi]).getValueDisp(new Point(xval,yval));
                  tmp=((CellRaster)arow.cells[oi]).getData(gc.label.toLowerCase());
                } else if (gc.cellType == T_STRING) {
                  tmp=((CellString)arow.cells[oi]).getData();
                }
              }
            }
          }
        }
        MW.setAltTitle(tmp);
      } catch (ArrayIndexOutOfBoundsException aobe) { }
    } catch (Exception e) { System.out.println("Exception in strecthColumn:"+e); e.printStackTrace(); }
  }

  private void processMouse1Event(int xval, int yval) {
    try {
      Column gc;
      if ((xval<=0) && (yval<=0)) return;
      int selRow = (yval-columnbuttonheight+(startrow*rowheight))/rowheight;
      if (selRow>currows) return;

      try {
        //Process row and column button selection first
        if (rowlabelson && (xval<rowbuttonwidth-1)) {
          processSelection("ROW",selRow,-1);
        } else if (collabelson && (yval<columnbuttonheight)) {
          Row arow = rows.elementAt(selRow);
          for (int i=startcol, colpos=(rowlabelson)?rowbuttonwidth:0; i<arow.count; i++) {
            gc = columns.elementAt(i);
            if (gc.visible) {
              if (xval>=colpos && xval<=colpos+gc.width)
                processSelection("COLUMN",-1,i);
              colpos += gc.width;
            }
          }
        } else {
          if (curmode == T_ROW) {
            processSelection("ROW",selRow,-1);
          } else {
            Row arow = rows.elementAt(selRow);
            for (int i=startcol, colpos=(rowlabelson)?rowbuttonwidth:0; i<arow.count; i++) {
              gc = columns.elementAt(i);
              if (curmode == T_COL) {
                if ((xval>=colpos) && (xval<=colpos+gc.width)) {
                  if (gc.visible) processSelection("COLUMN",-1,i);
                  return;
                } else
                  colpos += gc.width;
              } else {
                int oi = gc.origIndex;
                if (arow.cells[oi].inXBounds(xval)) {
                  if (gc.cellType == T_RASTER) {
                    boolean found = ((CellRaster)arow.cells[oi]).getValue(new Point(xval,yval));
                  } else if (gc.cellType == T_TOGGLE) {
                    arow.cells[oi].toggle();
                  }
                  processSelection("CELL",selRow,i);
                }
              }
            }
          }
        }
      } catch (ArrayIndexOutOfBoundsException aobe) { }
    } catch (Exception e) { System.out.println("Exception in processMouse1Event:"+e); e.printStackTrace(); }
  }

  //Stolen from MWindow class, 0=normal, -1="depress"
  private void shadowbox (int x, int y, int w, int h, int mode) {
   if(DEBUG_REFRESH) { System.out.println("  shadowbox(..)"); }
   try {
    if (g==null) return;
    if (mode==0) g.setColor (MW.theme.cwbg); else g.setColor(MW.theme.cwbg); //(MW.theme.cwts);
    if (mode==-2) {
      g.fillRect (x,y,w,h);
      g.setColor (MW.theme.cfg);
      return;
    }
    if (mode==0 || mode==-1) g.fillRect (x,y,w,h);
    if (mode>=0) g.setColor (MW.theme.cwts); else g.setColor(MW.theme.cwbs);
    g.drawLine (x,y,x+w-1,y);
    g.drawLine (x,y+1,x+w-2,y+1);
    g.drawLine (x,y,x,y+h-1);
    g.drawLine (x+1,y,x+1,y+h-2);
    if (mode>=0) g.setColor (MW.theme.cwbs); else g.setColor(MW.theme.cwts);
    g.drawLine (x,y+h-1,x+w-1,y+h-1);
    g.drawLine (x+1,y+h-2,x+w-1,y+h-2);
    g.drawLine (x+w-1,y,x+w-1,y+h-1);
    g.drawLine (x+w-2,y+1,x+w-2,y+h-1);
    if (mode==2) {
      g.setColor (MW.theme.cbg);
      g.drawLine (x+2,y+2,x+w-3,y+h-3);
    }
    g.setColor (MW.theme.cfg);
   } catch (Exception e) { System.out.println("Exception in shadowbox:"+e); e.printStackTrace(); }
  }

  private void renumberRows(boolean repaint) {
    try {
      for (int i=0; i<currows; i++) {
        Row arow = rows.elementAt(i);
        arow.label = ""+(i+1);
        rowbuttonwidth = Math.max(rowbuttonwidth,(arow.label.length()+1)*charwidth);
      }
      if (repaint) qrefresh();
    } catch (Exception e) { System.out.println("Exception in renumberRows:"+e); e.printStackTrace(); }
  }

  /** get column index by name
   *
   *  @param subrName   column  name
   *  @param ignorecase true if should ignore case
   *  @return column index
   */
  public int getColIndex(String subrName,boolean ignorecase) {
    try {
      for (int i=0; i<curcols; i++) {
        Column gc = columns.elementAt(i);
        if (!ignorecase && subrName.equals(gc.subr)) return gc.origIndex;
        else if (ignorecase && subrName.equalsIgnoreCase(gc.subr)) return gc.origIndex;
      }
    } catch (Exception e) { System.out.println("Exception in getColIndex:"+e); e.printStackTrace(); }
    return -1;
  }

  /** get column index by name
   *
   *  @param subrName column  name
   *  @return column index
   */
  public int getColIndex(String subrName) {
    try {
      return getColIndex(subrName,false);
    } catch (Exception e) { System.out.println("Exception in getColIndex:"+e); e.printStackTrace(); }
    return -1;
  }

  private Column getColByName(String subrName,boolean ignorecase) {
    try {
      Column gc;
      for (int i=0; i<curcols; i++) {
        gc = columns.elementAt(i);
        if (ignorecase && subrName.equalsIgnoreCase(gc.subr)) return gc;
        else if (!ignorecase && subrName.equals(gc.subr)) return gc;
      }
    } catch (Exception e) { System.out.println("Exception in getColByName:"+e); e.printStackTrace(); }
    return null;
  }

  private Column getColByName(String subrName) {
    try {
      return getColByName(subrName,false);
    } catch (Exception e) { System.out.println("Exception in getColByName:"+e); e.printStackTrace(); }
    return null;
  }

  private Row getRowByIndex(int index) {
    try {
      Row arow = null;
      try {
        if (rows != null) { // Since NeXtMidas 3.5.0 Proceed only if not null BUG 2817
          arow = rows.elementAt(index-1);
        }
      } catch (ArrayIndexOutOfBoundsException aie) {
        //System.out.println("Row index out of bounds: "+index);
      }
      return arow;
    } catch(Exception e) { System.out.println("Exception in getRowByIndex;"+e); e.printStackTrace(); return null; }
  }

  private void setSelect(int rowIdx, int colIdx) {
      this.selRow = rowIdx;
      this.selColumn = colIdx;
  }


  //*****************************************************************************
  //**** Public methods (Mainly)
  //*****************************************************************************
  //TBD...
  //public void   setColumn(int colIdx, Table t)
  //public void   setColumn(String subrName, Table t)
  /** Get the Multi Select mode
   *  @return the Multi Select mode */
  public int getMultiSelMsgMode () {
    return MultiSelMsgMode;
  }
  /** Set the Multi Select mode
   *  @param ms_mode the Multi Select mode */
  public void setMultiSelMsgMode (String ms_mode) {
    if (ms_mode.equals("FULL")) {
      setMultiSelMsgMode(MULTISEL_FULL);
    } else {
      setMultiSelMsgMode(MULTISEL_OFF);
    }
  }
  /** Set the Multi Select mode
   *  @param ms_mode the Multi Select mode  */
  public void setMultiSelMsgMode (int ms_mode) {
     if (ms_mode == MULTISEL_LIST) {
       System.out.println("WARNING: Multi select mode LIST "+ms_mode
                          +" is TBD. Setting mode OFF.");
       MultiSelMsgMode = MULTISEL_OFF;
     } else {
       MultiSelMsgMode = ms_mode;
     }
  }

  /** TRUE is in MULTI selection mode, FALSE otherwise
   *  @return TRUE is in MULTI selection mode, FALSE otherwise */
  public boolean getMultiSelMode () { return (!selectOne || selectRange); } // Get the Multi Select mode - MSZ

  /** get if grid lines are on
   *  @return true if grid lines are on
   */
  public boolean getGrid() { return(gridon);}

  /** get row count
   *  @return number of rows */
  public int getRowCount() { return rows.size(); }

  /** get column count
   *  @return number of columns */
  public int getColCount() { return columns.size(); }

  /** get if column labels are visible
   *  @return true if column labels are visible */
  public boolean getColLabelsVisible() { return(collabelson);}

  /** get if row labels are visible
   *  @return true if row labels are visible */
  public boolean getRowLabelsVisible() { return(rowlabelson);}

  /** get row height
   *  @return row height */
  public int getRowHeight() { return (rowheight); }

  /** Return the highlight color as an RGB value.
   *  @return the RGB value of the highlight color */
  public int getHighlight() { return defaultsel.getRGB(); }

  /** Return the output type used to specify the format of message data.
   *  @return the output type used to specify the format of message data */
  public int getOutputType() { return outtype; }

  /** get cell
   *
   *  @param row    row index
   *  @param column column index
   *  @return cell as Object
   */
  public Object getCell(int row, int column) {
      Object obj = null;
      Column gc  = null;
      Row arow   = null;

      try {
        gc = columns.elementAt(column-1);
      } catch (ArrayIndexOutOfBoundsException aie) {
        System.out.println("Column index out of bounds: "+column);
        return obj;
      }
      try {
        arow = rows.elementAt(row-1);
      } catch (ArrayIndexOutOfBoundsException aie) {
        //System.out.println("Row index out of bounds: "+row);
        return obj;
      }

      obj = arow.cells[gc.origIndex].getDataObj();
      return obj;
  }

  /** get Cell
   *
   *  @param rowIndx  row index
   *  @param subrName column name
   *  @return cell as Object
   */
  public Object getCell(int rowIndx, String subrName) {
      Object obj = null;
      Row arow   = null;

      try {
        arow = rows.elementAt(rowIndx-1);
      } catch (ArrayIndexOutOfBoundsException aie) {
        //System.out.println("Row index out of bounds: "+rowIndx);
        return obj;
      }
      int colIndx = getColIndex(subrName);
      if (colIndx != -1)
        obj = arow.cells[colIndx].getDataObj();

      return obj;
  }

  /** Depending on what the output type is set to, return either
   *  a Vector or Table with the row data.
   *  @param index index of row
   *  @param type  output type
   *  @return either a Vector or Table with row data
   */
  public Object getRow(int index, int type) {
      Object obj = null;
      Row arow = getRowByIndex(index);
      if (arow != null) {
        switch (type) {
          case T_TABLE:
            obj = (Object)arow.toTable(); break;
          default:
            obj = (Object)arow.rowData; break;
        }
      }
      return obj;
  }

  /** get row as Vectore cast to Object
  *
  *  @param index index of row
  *  @return row as Vector cast to Object
  */
  public Object getRowVector(int index) {
      return getRow(index, T_VECTOR);
  }

  /** get row as Table cast to Object
   *
   *  @param index index of row
   *  @return row as Table cast to Object
   */
  public Object getRowTable(int index) {
      return getRow(index, T_TABLE);
  }

  /** get row as default output type
   *
   *  @param index index of row
   *  @return row as default output type
   */
  public Object getRow(int index) {
      return getRow(index, outtype);
  }

  /** get selected row data as default output type
   *  @return selected row data as default output type
   */
  public Object getSelectedRowData() {
      if (selRow == -1) return null;
      return getRow(selRow);
  }

  /** get all rows in the output type specified
   *
   *  @param type output type
   *  @return all rows
   */
  public Object getRows(int type) {
    Object obj = null;
    switch (type) {
      case T_TABLE:
        obj = getAllRows();
        break;
      default:
        obj = getRowsVector();
    }
    return obj;
  }

  /** get all rows as a Vector cast to an Object
   *  @return all rows
   */
  public Object getRowsVector() {
    int numRows = rows.size();
    Vector<Object> vov = new Vector<>(numRows);
    for (int i=0; i<numRows; i++) {
      vov.addElement(getRowVector(i+1));
    }
    return vov;
  }

  /** get all rows as a Table cast to an Object
   *  @return all rows
   */
  public Object getRowsTable() {
    return getAllRows();
  }

  /** get all rows as a Table cast to an Object
   *  @return all rows
   */
  public Object getAllRows(){
      Table retTable = new Table();
      try {
         Row arow = null;
         currows = rows.size();
         for (int i=0;i<currows;i++) {
            arow = rows.elementAt(i);
            String tmpstr = ""+(i+1);
            retTable.put(tmpstr,getRowTable(i+1));
         }
      } catch (Exception e) {
         System.out.println("Exception in getAllRows:"+e);
         e.printStackTrace();
      }
      return (Object)retTable;
  }


  /** get row by value
   *
   *  @param t table containing column and value
   *  @return row index
   */
  public int getRowByValue(Table t) {
      Vector<?> v;
      int index = 0;

      Object obj = t.get("COLUMN");
      if (obj instanceof String) {
        String subrName = Convert.o2s(obj);
        v = (Vector<?>)getColumnVector(subrName);
      } else {
        int colIndx = Convert.o2l(obj);
        v = (Vector<?>)getColumnVector(colIndx);
      }

      Object val = t.get("VALUE");
      int vsize = v.size();
      for (int i=0; i<vsize; i++) {
        if (v.elementAt(i).equals(val)) return i+1;
      }
      return index;
  }

  /** Depending on what the output type is set to, return either
   *  a Vector or Table with the column data.
   *  @param colIdx column index
   *  @param type   output type
   *  @return either a Vector or Table with the column data
   */
  public Object getColumn(int colIdx, int type) {
      Object obj = null;
      Column  gc = null;
      try {
        gc = columns.elementAt(colIdx-1);
      } catch (ArrayIndexOutOfBoundsException aie) {
        System.out.println("Column index out of bounds: "+colIdx);
        return obj;
      }
      switch (type) {
        case T_TABLE:
          obj = (Object)gc.toTable(rows); break;
        default:
          obj = (Object)gc.toVector(rows); break;
      }
      return obj;
  }

  /** get column as specified type
   *
   *  @param subrName column name
   *  @param type     output type
   *  @return column in specified output type
   */
  public Object getColumn(String subrName, int type) {
      Object obj = null;
      Column gc = getColByName(subrName);
      if (gc != null) {
        switch (type) {
          case T_TABLE:
            obj = (Object)gc.toTable(rows); break;
          default:
            obj = (Object)gc.toVector(rows); break;
        }
      }
      return obj;
  }

  /** get column as default output type
   *
   *  @param colIdx colIdx column index
   *  @return column as default output type
   */
  public Object getColumn(int colIdx) {
      return getColumn(colIdx, outtype);
  }
  /** get column as table
   *
   *  @param colIdx column index
   *  @return column as table
   */
  public Object getColumnTable(int colIdx) {
      return getColumn(colIdx, T_TABLE);
  }
  /** get column as vector
   *
   *  @param colIdx column index
   *  @return column as vector
   */
  public Object getColumnVector(int colIdx) {
      return getColumn(colIdx, T_VECTOR);
  }
  /** get column as default output type
  *
  *  @param subrName column name
  *  @return column as default output type
  */
  public Object getColumn(String subrName) {
      return getColumn(subrName, outtype);
  }
  /** get column as table
  *
  *  @param subrName column name
  *  @return column as table
  */
  public Object getColumnTable(String subrName) {
      return getColumn(subrName, T_TABLE);
  }
  /** get column as vector
   *
   *  @param subrName column name
   *  @return column as vector
   */
  public Object getColumnVector(String subrName) {
      return getColumn(subrName, T_VECTOR);
  }

  /** get selected column data
   *  @return Object containing column data
   */
  public Object getSelectedColumnData() {
      if (selColumn == -1) return null;
      return getColumn(selColumn);
  }

  /** get selected rows as an Object that is a Table
   *  @return selected rows as an Object
   */
  public Object getSelRowsTable() {
    Table retTable = new Table();
    try {
      Row arow = null;
      currows = rows.size();
      for (int i=0;i<currows;i++) {
        arow = rows.elementAt(i);
        if (arow.selected) {
          String tmpstr = ""+(i+1);
          retTable.put(tmpstr,getRowTable(i+1));
        }
      }
    } catch (Exception e) {
      System.out.println("Exception in getSelRowsTable:"+e);
      e.printStackTrace();
    }
    return retTable;
  }

  /** get selected rows as a Vector
   *  @return selected rows as a Vector
   */
  public Object getSelRowsVector() {
    Vector<Object> retVec= new Vector<>();
    try {
      Row arow = null;
      currows = rows.size();
      for (int i=0;i<currows;i++) {
        arow = rows.elementAt(i);
        if (arow.selected)
          retVec.addElement(getRowVector(i+1));
      }
    } catch (Exception e) {
      System.out.println("Exception in getSelRowsVector:"+e);
      e.printStackTrace();
    }
    return (Object)retVec;
  }

  /** Returns the selected row information in an XML string format.
   *  @return the selected row information in an XML string format. */
  public String getSelectedRows()
  {
      String returnvalue = new String();
      returnvalue = "<SELECTED>";
      try {
          Row arow = null;
          currows = rows.size();
          for (int i=0;i<currows;i++) {
              arow = rows.elementAt(i);
              if (arow.selected) {
                  returnvalue += "<ROW "+(i+1)+">";
                  for (int j=0;j<arow.count;j++) returnvalue += "<VALUE "+(j+1)+">"+arow.cells[j].getData()+"</VALUE "+(j+1)+">";
                  returnvalue += "</ROW "+(i+1)+">";
              }
          }
      } catch (Exception e) {
          System.out.println("Exception in getSelectedRows:"+e);
          e.printStackTrace();
      }
      returnvalue += "</SELECTED>";
      return returnvalue;
  }

  /** get selected rows as a string
   *
   *  @param tagname tag name
   *  @return string version of a table reperesenting selected rows
   */
  public String getSelectedRows(String tagname) {
    Column gc;
    String tgc;
    String returnvalue=null;
    try {
      currows = rows.size();
      if (currows>0) {
        returnvalue = "<"+tagname+">";
        Row arow = null;
        for (int i=0;i<currows;i++) {
          arow = rows.elementAt(i);
          if (arow.selected) {
            returnvalue += "<ROW ";
            for (int j=0;j<arow.count;j++) {
              gc = columns.elementAt(j);
              tgc=gc.label.replaceAll(" ","_");
              returnvalue += tgc+"=\""+arow.cells[j].getData()+"\" ";
            }
            returnvalue += " />";
          }
        }
        returnvalue += "</"+tagname+">";
      }
    } catch (Exception e) {
        System.err.println("Exception in getSelectedRows:"+e);
        e.printStackTrace();
    }
    return returnvalue;
  }

  /** get selected rows as a Table
   *  @return selected rows as a Table
   */
  public Table getSelectedRowsTable() {
    Table t = new Table();
    Column gc;
    String tgc;
    try {
      Row arow = null;
      currows = rows.size();
      int c=1;
      for (int i=0;i<currows;i++) {
        arow = rows.elementAt(i);
        if (arow.selected) {
          Table r = new Table();
          for(int j=0;j<arow.count;j++) {
            gc = columns.elementAt(j);
            tgc = gc.label;
            r.put(tgc.toUpperCase(),arow.cells[j].getData());
          }
          r.put("ROWNUM",(i+1));
          t.put("ROW_"+c,r);
          c++;
        }
      }
    } catch (Exception e) {
      System.out.println("Exception in getSelectedRowsTable:"+e);
      e.printStackTrace();
    }
    return t;
  }

  /** Return the index of the selected row.
   *  @return index of the selected row */
  public int getSelectedRow() { return selRow; }

  /** Return whether the given row is selected or not
   *  @param i index of row
   *  @return true if selected
   */
  public boolean isRowSelected(int i) { if ((rows.elementAt(i-1)).selected) return true; else return false; }

  /** Return whether the given row is selected or not
   *  @param i row index
   *  @return 1 if selected, 0 if not
   */
  public int getRowSelected(int i) {
    //nxm.sys.lib.getContext().deprecate("Use isRowSelected() instead");
    if ((rows.elementAt(i-1)).selected) return 1; else return 0; }

  /** Return the index of the selected column.
   *  @return the index of the selected column */
  public int getSelectedColumn() { return selColumn; }

  /** Return the index of the selected row, column.
   *  @return the index of the selected row, column */
  public Table getSelectedCell() {
    Table t = new Table();
    t.put("ROW", selRow);
    t.put("COLUMN", selColumn);
    return t;
  }

  /** Return the contents of the currently selected cell.
   *  @return the contents of the currently selected cell */
  public Object getSelectedCellData() {
      if (selRow == -1 || selColumn == -1) return null;
      return getCell(selRow,selColumn);
  }

  /** Return a vector of panel pointers for the specified column
   *  @param subrName sub-record name
   *  @return a vector of panel pointers for the specified column
   */
  public Vector<?> getColumnPanels(String subrName) {
    Vector<Object> retVec = new Vector<>();
    Column gc = getColByName(subrName);
    if (gc != null) {
      if (gc.cellType != T_PANEL)
        retVec = changeColumnRender(subrName, T_PANEL);
      else {
        for (int j=0; j<currows; j++) {
          Row arow = rows.elementAt(j); // arow is unused (TODO is this a bug?)
          CellPanel cp = new CellPanel(MW,this,MW.theme.cbg,MW.theme.cfg);
          retVec.addElement(cp.cellPanel);
        }
      }
    }
    return retVec;
  }

  /** change column render
   *
   *  @param subrName sub-record (column) name
   *  @param type     int with cell type
   *  @return a vector of panel pointers for the specified column
   */
  public Vector<Object> changeColumnRender(String subrName, int type) {
    Vector<Object> retVec = new Vector<>();
    int changeIdx = 0;
    Column gc = getColByName(subrName);
    if (gc != null) {
      changeIdx = gc.origIndex;
      if (gc.cellType == type)
        return null;
      else
        gc.cellType = type;
    }

    for (int i=0; i<currows; i++) {
      Row arow = rows.elementAt(i);
      if (type == T_PANEL) {
        CellPanel cp = new CellPanel(MW,this,MW.theme.cbg,MW.theme.cfg);
        retVec.addElement(cp.cellPanel);
        cp.setData(arow.cells[changeIdx].getData());
        arow.cells[changeIdx]=(CellRenderer)cp;
      } else if (type == T_RASTER) {
        CellRaster cr = new CellRaster(MW);
        arow.cells[changeIdx]=(CellRenderer)cr;
        cr.setData(arow.cells[changeIdx].getData());
        arow.rowData.setElementAt(arow.cells[changeIdx].getDataObj(), changeIdx-1);
      } else if (type == T_TOGGLE) {
        CellToggle ct = new CellToggle(MW.theme.cbg,MW.theme.cfg);
        arow.cells[changeIdx]=(CellRenderer)ct;
        ct.setData(arow.cells[changeIdx].getData());
        arow.rowData.setElementAt(arow.cells[changeIdx].getDataObj(), changeIdx-1);
      } else if (type == T_TIME) {
        CellString cs = new CellString(MW.theme.cbg,MW.theme.cfg,gc.timeFormat);
        retVec.addElement(cs.getData());
        if (arow.cells[changeIdx] instanceof CellPanel) {
          CellPanel cp = (CellPanel)arow.cells[changeIdx];
          cp.cellPanel.setBackground(MW.theme.cbg);
          this.remove((Component)cp.cellPanel);
        }
        cs.setData(arow.cells[changeIdx].getData());
        arow.cells[changeIdx]=(CellRenderer)cs;
      } else {
        CellString cs = new CellString(MW.theme.cbg,MW.theme.cfg);
        retVec.addElement(cs.getData());
        if (arow.cells[changeIdx] instanceof CellPanel) {
          CellPanel cp = (CellPanel)arow.cells[changeIdx];
          cp.cellPanel.setBackground(MW.theme.cbg);
          this.remove((Component)cp.cellPanel);
        }
        cs.setData(arow.cells[changeIdx].getData());
        arow.cells[changeIdx]=(CellRenderer)cs;
      }
    }
    qrefresh();
    return retVec;
  }

  /** set raster data
   *
   *  @param t   table containing information on which cell raster to set
   *  @param buf byte buffer containing the data
   */
  public void setRasterData(Table t, byte[] buf) {
      CellRaster cr;
      int rowIndx = Convert.o2l(t.get("ROW"));
      Object obj = t.get("COLUMN");
      if (obj instanceof String) {
        String subrName = Convert.o2s(obj);
        cr = (CellRaster)getCell(rowIndx-1, subrName);
      } else {
        int colIndx = Convert.o2l(obj);
        cr = (CellRaster)getCell(rowIndx-1, colIndx);
      }
      cr.setData(buf);
      qrefresh();
  }

  /** set cell using information in a Table
   *  @param t the Table to use
   */
  public void setCell(Table t) {
      Object obj = t.get("VALUE");
      if (obj == null) return;
      int rowIdx = Convert.o2l(t.get("ROW"));
      if (rowIdx == -2) rowIdx = getSelectedRow();
      if (rowIdx == -1) return;
      Object colObj = t.get("COLUMN");
      if (colObj == null) colObj = t.get("NAME");
      if (colObj instanceof String) {
        String subrName = Convert.o2s(colObj);
        setCell(rowIdx, subrName, obj);
      } else {
        int colIdx = Convert.o2l(colObj);
        setCell(rowIdx, colIdx, obj);
      }
  }

  /** set cell value
   *
   *  @param rowIdx row index
   *  @param colIdx column index
   *  @param val    value
   */
  public void setCell(int rowIdx, int colIdx, Object val) {
      Column gc;
      Row arow;
      try {
        gc = columns.elementAt(colIdx-1);
      } catch (ArrayIndexOutOfBoundsException aie) {
        System.out.println("Column index out of bounds: "+colIdx);
        return;
      }
      try {
        if (rowIdx == -2) rowIdx = getSelectedRow();
        if (rowIdx == -1) return;
        arow = rows.elementAt(rowIdx-1);
      } catch (ArrayIndexOutOfBoundsException aie) {
        //System.out.println("Row index out of bounds: "+rowIdx);
        return;
      }

      if (gc.cellType == T_STRING || gc.cellType == T_TIME) {
        //arow.cells[gc.origIndex].setData(gc.getColorRule().getData(val.toString()).trim());
        arow.cells[gc.origIndex].setData(gc.getColorRule().getData(val.toString())); // 4.1.0 Jeff update
        arow.rowData.setElementAt(val, gc.origIndex);
        int rs = arow.rowData.size();
        arow.cells[gc.origIndex].setColors(gc.getColorRule().getColor(arow.rowData.subList(0,rs)),arow.cells[gc.origIndex].getDefBg());
      } else if (gc.cellType == T_TOGGLE) {
        arow.cells[gc.origIndex].setDataObj(val);
        arow.rowData.setElementAt(val, gc.origIndex);
      } else if (gc.cellType == T_RASTER) {
        arow.cells[gc.origIndex].setDataObj(val);
      }
      qrefresh();
  }

  /** set cell
   *
   *  @param rowIdx   row index
   *  @param subrName subrecord (i.e. column) name
   *  @param val      value
   */
  public void setCell(int rowIdx, String subrName, Object val) {
      Row arow;
      Column gc = getColByName(subrName);
      if (gc != null) {
        try {
          if (rowIdx == -2) rowIdx = getSelectedRow();
          if (rowIdx == -1) return;
          arow = rows.elementAt(rowIdx-1);
        } catch (ArrayIndexOutOfBoundsException aie) {
          //System.out.println("Row index out of bounds: "+rowIdx);
          return;
        }
        if (gc.cellType == T_STRING || gc.cellType == T_TIME) {
          //arow.cells[gc.origIndex].setData(gc.getColorRule().getData(val.toString()).trim());
          arow.cells[gc.origIndex].setData(gc.getColorRule().getData(val.toString())); // 4.1.0 Jeff update
          arow.rowData.setElementAt(val, gc.origIndex);
          int rs = arow.rowData.size();
          arow.cells[gc.origIndex].setColors(gc.getColorRule().getColor(arow.rowData.subList(0,rs)),arow.cells[gc.origIndex].getDefBg());
        } else if (gc.cellType == T_TOGGLE) {
          arow.cells[gc.origIndex].setDataObj(val);
          arow.rowData.setElementAt(val, gc.origIndex);
        } else if (gc.cellType == T_RASTER) {
          arow.cells[gc.origIndex].setDataObj(val);
        }
      }
      qrefresh();
  }

  /** Turn the auto selection on or off.
   *  @param autoon true if auto selection is on */
  public void setAutoSelect (boolean autoon) { autoSelect = autoon; }

  /** Turn the grid on or off.
   *  @param on true if grid is on */
  public void setGrid(boolean on) {
    gridon = on;
    qrefresh();
  }

  /** Set the template.
   *  @param keys the template keys */
  public void setTemplate(Keywords keys) {
    templateKeys = keys;
    qrefresh();
  }

  /** Show/hide a column.  Specify the column by its four character subrecord name
   *  @param subrName sub-record name
   *  @param onoff    true if on
   */
  public void setColVisible(String subrName, boolean onoff) {
    for (int i=0; i<curcols; i++) {
      Column gc = columns.elementAt(i);
      if (subrName.equals("ALLCOLUMNS")) gc.visible = onoff;
      else if (subrName.equals(gc.subr))
        gc.visible = onoff;
    }
    qrefresh();
  }

  /** Set the column width.
   *  @param subrName sub-record name
   *  @param width    column width
   */
  public void setColWidth(String subrName, int width) {
    for (int i=0; i<curcols; i++) {
      Column gc = columns.elementAt(i);
      if (subrName.equals(gc.subr))
        gc.width = width;
    }
    qrefresh();
  }

  /** Set the column width.
   *  @param subrName sub-record name */
  public void setColWidth(String subrName) {
    for (int i=0; i<curcols; i++) {
      Column gc = columns.elementAt(i);
      if (subrName.equals(gc.subr))
        gc.width = gc.origWidth;
    }
    qrefresh();
  }
  /** Get the column width
   *  @param subrName sub-record name
   *  @return -1 SUBRECORD if not found  */
  public int getColWidth(String subrName) {
    Column gc = null;
    for (int i=0; i<curcols; i++) {
      gc = columns.elementAt(i);
      if (subrName.equals(gc.subr)) break;
    }
    if (gc == null) return -1;
    else            return gc.width;
  }

  /** Set the cursor mode attribute.
   *  @param value the cursor mode */
  public void setCursorMode(int value) {
    curmode = value;
    deselectAll();
    qrefresh();
  }

  /** Set the highlight color to an RGB value.
   *  @param value the RGB of the highlight color */
  public void setHighlight(int value) {
    defaultsel =  MColor.getColorByIndex(value);
    qrefresh();
  }

  /** Set the highlight color
   *  @param clr the highlight Color */
  public void setHighlight(Color clr) {
    defaultsel =  clr;
    qrefresh();
  }

  /** Set the highlight color to one of the defined colors in the MColor table.
   *  @param value the highlight color in string form */
  public void setHighlight(String value) {
    defaultsel = MColor.getColor(value);
    qrefresh();
  }

  /** Set the color of a cell.
   *  @param column the column
   *  @param row    the row
   *  @param fg     the foreground color
   *  @param bg     the background color
   */
  public void setCellColors(int column, int row, Color fg, Color bg) {
    int colToUse = column-1;
    for (int j=0; j<columns.size(); j++)  {
      Column gc = columns.elementAt(j);
      if (gc.field == column)  colToUse = gc.origIndex;
    }
    Row arow = rows.elementAt(row-1);
    if (fg==null) fg = MW.theme.getForeground();
    if (bg==null) bg = MW.theme.getBackground();
    arow.cells[colToUse].setColors(fg, bg);
    qrefresh();
  }

  /** Set the color of a row.
   *  @param row the row
   *  @param fg  the foreground color
   *  @param bg  the background color
   */
  public void setRowColors(int row, Color fg, Color bg) {
    if (fg==null) fg = MW.theme.getForeground();
    if (bg==null) bg = MW.theme.getBackground();
    Row arow = rows.elementAt(row-1);
    for (int i=0; i<arow.count; i++) {
      arow.cells[i].setColors(fg, bg);
    }
    qrefresh();
  }

  /** Set the color of a column.
   *  @param column the column
   *  @param fg     the foreground color
   *  @param bg     the background color
   */
  public void setColColors(int column, Color fg, Color bg) {
      if (fg==null) fg = MW.theme.getForeground();
      if (bg==null) bg = MW.theme.getBackground();
      int colToUse = column-1;
      for (int j=0; j<columns.size(); j++)  {
        Column gc = columns.elementAt(j);
        if (gc.field == column)  {
          colToUse = gc.origIndex;
        }
      }
      for (int i=0; i<rows.size(); i++)  {
        Row arow = rows.elementAt(i);
        arow.cells[colToUse].setColors(fg, bg);
      }
      qrefresh();
  }

  /** Set the width of all row buttons to the given integer pixel value.
   *  @param prbsize  the width of all row buttons to the given integer pixel value. */
  public void setRowButtonWidth(int prbsize) {
      try {
          if (prbsize>=0) rowbuttonwidth = prbsize;
      } catch (RuntimeException e) {
          System.out.println("Exception in setRowButtonWidth:"+e);
          e.printStackTrace();
      }
  }

  /**
   * Set all of the color values for each of the table components to the currently
   * selected theme value.  This assumes that the theme has previously been set
   * in the MWindow.
   */
  public void setTheme() {
    if(MW.panel != null) {// Since NeXtMidas 3.5.0 Proceed only if not null BUG 2817
      MW.panel.setForeground(MW.theme.cfg);
      MW.panel.setBackground(MW.theme.cbg);
      if (horizontalScrollbar != null) { // Since NeXtMidas 3.5.0 Proceed only if not null BUG 2817
        horizontalScrollbar.setForeground(MW.theme.cfg);
        horizontalScrollbar.setBackground(MW.theme.cbg);
      }
      if (verticalScrollbar != null) { // Since NeXtMidas 3.5.0 Proceed only if not null BUG 2817
        verticalScrollbar.setForeground(MW.theme.cfg);
        verticalScrollbar.setBackground(MW.theme.cbg);
      }

      gridcolor   = MW.theme.cfg.darker().darker();
      MW.process ("COLOR",1,null);

      qrefresh();
    }
  }

 /** Set the column label justification: right, left, center (default)
  *  @param just column label justification
  */
  public void setColLabJus(String just) {
      String j = just.toUpperCase();
      if (j.equals("RIGHT"))     collabjus = T_RIGHT;
      else if (j.equals("LEFT")) collabjus = T_LEFT;
      else                       collabjus = T_CENTER;
      qrefresh();
  }

 /** Turn the column labels on or off.
  *  @param on true if column labels are to be on */
  public void setColLabels(boolean on)  {
      if (on) {
          columnbuttonheight = 20;    //default size save as rowheight
          collabelson = true;
      } else {
          columnbuttonheight = 0;
          collabelson = false;
      }
      qrefresh();
  }

  /** Set the column labels (starting with the first column) via an array of string
   *  values.
   *  @param plabels column labels in a String array
   */
  public void setColLabels(String[] plabels) { setColLabels(plabels, plabels.length, false); }

  /** Set the column labels (starting with the first column) via a list of token
   *  delimited strings.
   *  @param plabels token delimited string of labels
   */
  public void setColLabels(String plabels)  {
      try {
          StringTokenizer st = new StringTokenizer(plabels,delimiters,true);
          String[] arow= new String[st.countTokens()];
          int valuecount=0;
          boolean token=false;
          String s;

          while (st.hasMoreTokens()) {
              s = st.nextToken();
              if (delimiters.indexOf(s)<0) {
                  arow[valuecount]=s;
                  valuecount++;
                  token=false;
              } else {
                  if (token) {
                      arow[valuecount]="";
                      valuecount++;
                  }
                  token=true;
              }
          }
          setColLabels(arow, valuecount, true);
      } catch (Exception e) {
          System.out.println("Exception in setColLabels:"+e);
          e.printStackTrace();
      }
  }

  /** Turn the row labels on or off.
   *  @param on true if row labels are on */
  public void setRowLabels(boolean on) {
      if (on)  rowlabelson = true;
      else     rowlabelson = false;
      qrefresh();
  }

  /**
    Replace the row information at the specified row index with the values in
    the specified table.  The key values in the table must match the column subr
    name for the data to be replaced.  If index equals -1, simply append the row.
    @param index One-based index into list (1..n)
    @param t Table of replacement data
    @todo setRow, setRows, addRow and addRows should ALL use common code.
  */
  public void setRow(int index, Table t) {
      if (index < 0) {
        addRow(t);
      } else {
        int offset = index-1; // one-based
        Row arow = rows.elementAt(offset);
        Vector<Object> v = arow.rowData;
        for (int i=0; i<arow.count; i++) {
          Column gc = columns.elementAt(i);
          Object o = t.get(gc.subr);
          if (o != null) {
            v.setElementAt(o, i);
            String newVal = "";
            //format the new data if it's a numeric value,
            //otherwise get the String representation
            if (o instanceof Data) {
              byte datatype = ((Data)o).type;
              //if it's a numeric, widen to double and format
              if(datatype == Data.BYTE || datatype == Data.INT || datatype == Data.LONG ||
                 datatype == Data.XLONG || datatype == Data.FLOAT || datatype == Data.DOUBLE) {
//jph -- start DR 173, #508244-3
/*
                  newVal = gc.format(((Data)o).toD());
*/
                if (gc.getColorRule() instanceof EnumColorRule)  {
                  newVal = gc.getColorRule().getData(v.elementAt(i).toString()).trim();
                } else {
                  newVal = gc.format(((Data)o).toD());
                }
//jph -- end DR 173, #508244-3
              } //otherwise get string representation
              else newVal = ((Data)o).getValue();
            }
            else if (o instanceof java.lang.Number) {
              newVal = gc.format(((Number)o).doubleValue());
            }
//jph -- start DR 173, #508244-3 -- 13DEC05
//jph              else newVal = o.toString();
            else {
              String str = o.toString();
              if (gc.getColorRule() instanceof EnumColorRule && StringUtil.isInteger(str) )  {
                newVal = gc.getColorRule().getData(v.elementAt(i).toString()).trim();
              } else {
                newVal = str;
              }
//jph -- start DR 173, #508244-3 -- 13DEC05
            }
            arow.cells[i].setData(newVal);
          }
        }
        arow.rowData = v;
        try {
          rows.setElementAt(arow, offset);
          qrefresh();
        } catch (Exception e) {
          System.out.println("Exception in setRow:"+e);
          e.printStackTrace();
        }
      }
  }

  /** Set the format of the data for all output messages to either a table or
   *  vector format.
   *  @param type format of the data
   */
  public void setOutputType(int type) { this.outtype = type; }

  /** set if default highlighting of selections to be done by backlighting
   *  @param useFg true to enable
   */
  public void setFgHilite(boolean useFg) { this.fgHilite = useFg; qrefresh(); }
  /** set default selected color
   *  @param c default selected color
   */
  public void setSelectColor(Color c)    { this.defaultsel = c; }

  /** Add Columns
   *  @param plabels labels (as string with delimiters)
   */
  public void addColumns(String plabels)  {
      try {
          StringTokenizer st = new StringTokenizer(plabels,delimiters,true);
          String[] newCols= new String[st.countTokens()];
          int valuecount=0;
          boolean token=false;
          String s;

          while (st.hasMoreTokens()) {
              s = st.nextToken();
              if (delimiters.indexOf(s)<0) {
                  newCols[valuecount]=s.trim();
                  valuecount++;
                  token=false;
              } else {
                  if (token) {
                      newCols[valuecount]="";
                      valuecount++;
                  }
                  token=true;
              }
          }
          for (int i=curcols, j=0;j<valuecount;i++,j++) {
              Column gc = new Column();
              gc.label=newCols[j].trim();
              gc.subr=newCols[j].trim();
              gc.width=(newCols[j].length()+2)*charwidth;
              gc.index=Integer.valueOf(curcols);
              gc.origIndex=curcols;
              gc.cellType=T_STRING;
              gc.visible=true;
              columns.addElement(gc);
          }
          int endcol=curcols+valuecount;
          if (endcol>curcols) curcols=endcol;

          qrefresh();

      } catch (Exception e) {
          System.out.println("Exception in addColumns:"+e);
          e.printStackTrace();
      }
  }

  /** Add a row to the current data set with the values in the specified
   *  table.  For a cell to be populated in a row, a key in the table must
   *  match the cell's column label.
   *  @param t table containing data set
   */
  public void addRow(Table t) {
      Vector<Object> data = new Vector<>();
      for (int i=0; i<curcols; i++) {
          Column gc = columns.elementAt(i);
          Object o = t.get(gc.subr);
          if (o != null) {
              data.addElement(o);
          } else {
              data.addElement("");
          }
      }
      int rowPos = Convert.o2l(t.get("LIST__POS"));
      if (rowPos>0) addRows(data, curcols, true, rowPos);
      else addRows(data, curcols, true, 0);
  }

  /** Populate the table with a vector of values.  The rowlen parameter allows
   *  the data to be displayed with a variable frame length.  If the data is
   *  is an XMidas type 1000 file, the rowlen should be set to 1.
   *  @param values Vector of values to use in populating table
   *  @param rowlen allows data to be displayed with a vriable frame length
   */
  public void addRows(Vector<? super Object> values, int rowlen) {
      addRows(values, rowlen, false, 0);
      qrefresh();
  }

//jph -- I THINK THIS CAN BE DELETED
  /** TODO comment out and delete this method
   *  @param values values
   *  @param rowlen row length
   *  @deprecated This method has been deprecated as of NeXtMidas 2.1.1
   */
  @Deprecated public void pollRows(Vector<?> values, int rowlen) {
    nxm.sys.lib.Shell.getSharedMidasContext().deprecate("This method is no longer supported. See /POLL switch on LIST");
    pollRows(values, rowlen, false, 0);
    qrefresh();
  }

  /** Populate the table with a vector of vectors.
   *  @param vov Vector of Vectors to use in populating table */
  public void addRows(Vector<Vector<Object>> vov) {
      try {
        for (int i=0; i<vov.size(); i++) {
            Vector<Object> rowValues = vov.elementAt(i);
            int rowlen = rowValues.size();
            addRows(rowValues,rowlen,false, 0);
        }
        qrefresh();
      } catch (Exception e) { System.out.println("Exception in addRows:"+e); e.printStackTrace(); }
  }

  /** poll rows
   *  @param vov vector of vectors to poll
   */
  public void pollRows(Vector<Vector<Object>> vov) {
     for (int i=0; i<vov.size(); i++) {
         Vector<Object> rowValues = vov.elementAt(i);
         int rowlen = rowValues.size();
         pollRows(rowValues,rowlen,false,i);
     }
  }


  /** Delete the row at the specified index.
   *  @param row row index */
  public void removeRow(int row) {
      try {
          if (row == -2) row = getSelectedRow();
          if (row>currows || row <= 0) return;
          rows.removeElementAt(row-1);
          currows = rows.size();
          if (currows>0) {
            renumberRows(false);
            this.selRow = -1;
          } else
            deselectAll();
          qrefresh();
      } catch (Exception e) {
          System.out.println("Exception in removeRow:"+e);
          e.printStackTrace();
      }
  }

  /** Delete the row at the specified index.

      @param startRow The first row to remove (inclusive, row=1..n).
      @param endRow   The last row to remove (inclusive, row=1..n).

      @since NeXtMidas 1.9.4
   */
  public void removeRows(int startRow, int endRow) {
      try {
          for(int row = endRow; row >= startRow; row--) {
            rows.removeElementAt(row-1);
          }
          currows = rows.size();
          if (currows>0) {
            renumberRows(false);
            this.selRow = -1;
          } else
            deselectAll();
          qrefresh();
      } catch (Exception e) {
          System.out.println("Exception in removeRows:"+e);
          e.printStackTrace();
      }
  }

  /** Public method to clear the data on the screen.
   *  @param tmp unused */
  public void clear(int tmp) {
      rows.removeAllElements();
      currows = 0;
      startrow = 0;
      scrollEnd = scrollEndSw;
      qrefresh();
  }

  /** Clear all row and column vectors and reset all indexes with REFRESH */
  public void clear() { clear(true); }

  /** Clear all row and column vectors and reset all indexes.
      @param doRefresh Do refresh if true
  */
  public void clear(boolean doRefresh) {
      try {
          for (int i=0; i<currows; i++) {
            Row arow = rows.elementAt(i);
            for (int j=0; j<arow.count;j++) {
              if (arow.cells[j] instanceof CellPanel || arow.cells[j] instanceof CellRaster) {
                CellPanel cp = (CellPanel)arow.cells[j];
                this.remove((Component)cp.cellPanel);
              }
            }
          }
          if (rows != null) rows.removeAllElements(); // Since NeXtMidas 3.5.0 Proceed only if not null BUG 2817
          if (columns != null) columns.removeAllElements(); // Since NeXtMidas 3.5.0 Proceed only if not null BUG 2817
          selRow = -1;
          selColumn = -1;
          currows = 0;
          curcols = 0;
          startcol = 0;
          startrow = 0;
          scrollEnd = scrollEndSw;
          if(doRefresh)qrefresh();
          rowbuttonwidth = 39;
      } catch (Exception e) {
          System.out.println("Exception in clear:"+e);
          e.printStackTrace();
      }
  }

  /** Deselect any selected row, column or cell. */
  public void deselectAll() {
    deselectAll(false);
  }
  /** Deselect any selected row, column or cell.
   *  @param sendMsg true if should send message */
  public void deselectAll(boolean sendMsg) {
      boolean deselected=false;
      selectRange = false;
      lastRow = -1;
      Table msgDataTbl = new Table();
      msgDataTbl.put("TYPE","ROW"); // COL, CELL are TBD
      msgDataTbl.put("ROW", Integer.valueOf(selRow) );    // jph -- Not sure why I do not
      msgDataTbl.put("COLUMN", Integer.valueOf(selColumn)); // not have to increment selRow and Col??
      msgDataTbl.put("VALUE",getRowTable(selRow));

      Object preSelRowsObj = null;
      switch (outtype) {
        case T_TABLE:
          preSelRowsObj = new Table();
          if (MultiSelMsgMode == MULTISEL_FULL) {
            preSelRowsObj = getSelRowsTable();
          }
          break;
        default:
          if (MultiSelMsgMode == MULTISEL_FULL) {
            preSelRowsObj = getSelRowsVector();
          } else {
            preSelRowsObj = new Vector<>();
          }
          break;
      }
      if (MultiSelMsgMode != MULTISEL_OFF) {
        msgDataTbl.put("PREVSEL",preSelRowsObj);
      }

      try {
          Row arow;
          for (int i=0;i<currows;i++) {
              arow = rows.elementAt(i);
              if(arow.selected==true){
                deselected=true;
              }
              arow.selected = false;
              setSelect(-1, -1);
              for (int j=0; j<arow.count; j++) {
                Column gc = columns.elementAt(j);
                if(gc.selected==true) deselected=true;
                gc.selected = false;
                arow.cells[j].setSelected(false);
                columns.setElementAt(gc,j);
              }
              rows.setElementAt(arow,i);
          }
          qrefresh();
      } catch (Exception e) {
          System.out.println("Exception in deselectAll:"+e);
          e.printStackTrace();
      }

      if (sendMsg && deselected) {
        MW.process("DESELECT",0,msgDataTbl);
      }

  }

  /**
   * Select the cell at the specified row and column index.
   */
  private void selectCell(int row, int column) {
      try {
          if (row>currows || column>curcols) return;
          if (selectOne) deselectAll();
          Row arow = rows.elementAt(row-1);
          Column gc = columns.elementAt(column-1);
          if (arow.cells[column-1].isSelected() == true)  {
            arow.cells[column-1].setSelected(false);
            setSelect(-1,-1);
          }
          else  {
            arow.cells[column-1].setSelected(true);
            setSelect(row,column);
          }
          qrefresh();
      } catch (Exception e) {
          System.out.println("Exception in selectCell:"+e);
          e.printStackTrace();
      }
  }

  /** Public method for selecting a cell.  Uses one argument
   *  @param t table describing cells to select */
  public void selectCell(Table t) {
      int rowIndex = Convert.o2l(t.get("ROW"));
      int colIndex = Convert.o2l(t.get("COLUMN"));
      selectCell(rowIndex, colIndex);
  }

  /**
   * Select all rows
   */
  public void selectAll() {
      try {
          Row arow;
          for (int i=0;i<currows;i++) {
              arow = rows.elementAt(i);
              arow.selected = true;
              //setSelect(-1, -1);
              for (int j=0; j<arow.count; j++) {
                Column gc = columns.elementAt(j);
                gc.selected = true;
                arow.cells[j].setSelected(true);
                columns.setElementAt(gc,j);
              }
              rows.setElementAt(arow,i);
          }
          qrefresh();
      } catch (Exception e) {
          System.out.println("Exception in selectAll:"+e);
          e.printStackTrace();
      }
  }

  /** select row by column values
      @param colValues column values
      @since NeXtMidas 2.8.2
   */
  public void selectRowByColsValue (Table colValues) {
    try {
      deselectAll();
      if (colValues==null) return;
      String dKey;
      String dVal;
      int colnum;
      Table.Iterator ti;
      Row arow;
      boolean selectIt = false;
      for (int i=0; i<currows; i++) {
        arow = rows.elementAt(i);
        selectIt = false;
        for (ti=colValues.iterator(); ti.getNext();) {
          dKey = ti.key;
          dVal = Convert.o2s(ti.value);
          colnum = getColIndex(dKey,true);
          if (colnum<0) continue;
          if (!dVal.equalsIgnoreCase(arow.cells[colnum].getData())) {
            selectIt = false;
            break;
          } else selectIt = true;
        }
        if (selectIt) { arow.selected = true; setSelect(i,-1); }
      }
    } catch (Exception e) {
      System.out.println("Exception in method selectRowByColsValues: "+e);
      e.printStackTrace();
    }
  }

  /** select rows by column value
   *  @param colname    column name
   *  @param colvalue   column value
   *  @param ignoreCase ignore value
   *  @since NeXtMidas 2.8.2
   */
  public void selectRowsByColValue (String colname, String colvalue, boolean ignoreCase) {
    try {
      deselectAll();
      if (colname==null || colvalue==null) return;
      int colnum = getColIndex(colname,ignoreCase);
      if (colnum<0) return;
      Row arow;
      for (int i=0; i<currows; i++) {
        arow = rows.elementAt(i);
        if (ignoreCase && colvalue.equalsIgnoreCase(arow.cells[colnum].getData())) {
          arow.selected = true;
          setSelect(i,-1);
        }
      }
      qrefresh();
    } catch (Exception e) {
      System.out.println("Exception in method selectRowsByColValue: "+e);
      e.printStackTrace();
    }
  }

  /** select rows that have a certain column value
   *
   *  @param colname    column name
   *  @param colvalue   column value
   *  @param ignorecase true to ignore case
   *  @return -1 if no rows were selected, otherwise last selected
   */
  public int selectRowByColValue(String colname,String colvalue,boolean ignorecase) {
      int selected = -1;
      try {
          if (selectOne) deselectAll();
          if (colname==null || colvalue==null) return selected;    // just deselect
          int colnum = getColIndex(colname,ignorecase);
          if (colnum<0) return selected;
          Row arow;
          for (int i=0;i<currows;i++) {
              arow = rows.elementAt(i);
              if (ignorecase && colvalue.equalsIgnoreCase(arow.cells[colnum].getData())) {
                arow.selected = true;
                selected = i;
                setSelect(selected,-1);
                if (selectOne) break;
              } else if (!ignorecase && colvalue.equals(arow.cells[colnum].getData())) {
                arow.selected = true;
                selected = i;
                setSelect(selected,-1);
                if (selectOne) break;
              }
          }

          if (selectOne && selected>-1) {
            int numVisRows = (MW.pos.h-columnbuttonheight-hscrlheight)/rowheight;
            if ((selected < startrow) || (selected > startrow+numVisRows))  {
              startrow = selected;
              if (startrow<0) {
                startrow=0;
              } else if (startrow>currows)  {
                startrow = currows;
              }
              verticalScrollbar.setValues(startrow,numVisRows,0,currows);
            }
          }
          qrefresh();
          return selected;
      } catch (Exception e) {
          System.out.println("Exception in selectRow:"+e);
          e.printStackTrace();
      }
      return selected;
  }

  /** Select the row at the specified row index.
   *  @param row row index */
  public void selectRow(int row) {
      try {
          // if (row>currows) return;
          if (selectOne) deselectAll();
          if (row<=0 || row>rows.size()) return;    // just deselect
          if (((rows.elementAt(row-1)).selected) == true)  {
            (rows.elementAt(row-1)).selected = false;

            setSelect(-1,-1);
          } else {
            (rows.elementAt(row-1)).selected = true;
            setSelect(row,-1);

            if((lastRow==-1)||(selectOne)) lastRow = row;
            if(selectRange){
              //System.out.println("Selecting Range from "+lastRow+" to "+row);
              if(lastRow==-1){
                //System.out.println("Not doing Range");
              } else {
                for(int zz=lastRow;zz<=row;zz++){
                  //System.out.println("Selecting "+zz);
                  setSelect(zz,-1);
                  (rows.elementAt(zz-1)).selected = true;
                }
              }
            }

          }
          int numVisRows = (MW.pos.h-columnbuttonheight-hscrlheight)/rowheight;
          if ((row-1 < startrow) || (row-1 > startrow+numVisRows))  {
            startrow = row-1;
            if (startrow<0) {
              startrow=0;
            } else if (startrow>currows)  {
              startrow = currows;
            }
            verticalScrollbar.setValues(startrow,numVisRows,0,currows);
          }
          qrefresh();
      } catch (Exception e) {
          System.out.println("Exception in selectRow:"+e);
          e.printStackTrace();
      }
  }

  /**  DeSelect the row at the specified index.
   @param row Row index 1...n
  */
  public void deSelectRow(int row) {
    try {
       if (row<=0 || row>rows.size()) return;
       if (((rows.elementAt(row-1)).selected) == true)  {
         (rows.elementAt(row-1)).selected = false;
         setSelect(-1,-1);
         qrefresh();
       }
    } catch (Exception e) {
       System.out.println("Exception in deSelectRow:"+e);
       e.printStackTrace();
    }
  }

  /** select row
   *
   *  @param row row number
   *  @param clear clear previous selections
   */
  public void selectRow(int row,boolean clear) {
      try {
          if (row>currows) return;
          if (clear) deselectAll();
          if (row<=0) return; // just deselect
          (rows.elementAt(row-1)).selected = true;
          setSelect(row,-1);
          qrefresh();
      } catch (Exception e) {
          System.out.println("Exception in selectRow:"+e);
          e.printStackTrace();
      }
  }

  /** Select the column at the specified column index.
   *  @param column column index */
  public void selectColumn (int column) {
      try {
          if (column>curcols) return;
          if (selectOne) deselectAll();
          Column gc = columns.elementAt(column-1);
          if (gc.selected == true)  {
            gc.selected = false;
            setSelect(-1,-1);
          }
          else  {
            gc.selected = true;
            setSelect(-1,column);
          }
          qrefresh();
      } catch (Exception e) {
          System.out.println("Exception in selectColumn:"+e);
          e.printStackTrace();
      }
  }

  /** Public method for selecting multiple cells.  Uses one argument.
   *  @param t table describing cells to select
   */
  public void selectCells(Table t) {
      String subrecs = Convert.o2s(t.get("NAMES")).toUpperCase();
      StringTokenizer st = new StringTokenizer(subrecs,delimiters,true);
      String[] eachsubr = new String[st.countTokens()];
      int valuecount=0;
      boolean token=false;
      String s;
      int selRow=0;
      int[] columnIndexs;

      while (st.hasMoreTokens()) {
        s = st.nextToken();
        if (delimiters.indexOf(s)<0) {
          eachsubr[valuecount]=s.trim();
          valuecount++;
          token=false;
        } else {
          if (token) {
            eachsubr[valuecount]="";
            valuecount++;
          }
          token=true;
        }
      }

      columnIndexs = new int[valuecount];
      for (int i=0; i<curcols; i++) {
        Column gc = columns.elementAt(i);
        for (int j=0; j<valuecount; j++) {
          if (gc.subr.equals(eachsubr[j])) columnIndexs[j]=i;
        }
      }

      int rowIndex = Convert.o2l(t.get("ROW"));
      selectCells(rowIndex, columnIndexs);

  }

  /**
   * Select multiple cells in a row.  Uses a row index and an
   * array of column indexs.
   */
  private void selectCells(int row, int[] columnIdxs) {
      if (row>currows) return;
      deselectAll();
      try {
        Row arow = rows.elementAt(row-1);
        for (int i=0; i<columnIdxs.length; i++) {
          int indx = columnIdxs[i];
          Column gc = columns.elementAt(indx);
          arow.cells[gc.origIndex].setSelected(true);
        }
        setSelect(row,columnIdxs[0]); //set "current" selection to first one in list, for now
        qrefresh();
      } catch (Exception e) {
          System.out.println("Exception in selectCells:"+e);
          e.printStackTrace();
      }
  }

  /** apply template */
  public void applyTemplate() {
    applyTemplate(templateKeys);
  }

  /** apply template
   *  @param keywds keywords
   */
  public void applyTemplate(Keywords keywds) {
//jph -- kludge to make sure list column sizing is correct when templates are used.
// without this the list columns size too wide when a stretch column is used.
if (refreshMode == RM_TIMER) {
  forceRefresh();
}
        Object obj, subrObj;
        String defaultColor;
        deselectAll();


        //Read the "global" format attributes
      if ((obj=keywds.getKey("GRID")) != null) {
        String g = Convert.o2s(obj);
        if (g.equals("OFF")) setGrid(false); else setGrid(true);
      }
      if ((obj=keywds.getKey("LABELS")) != null) {
        String clabs = Convert.o2s(obj);
        if (clabs.equals("OFF")) setColLabels(false); else setColLabels(true);
      }
      if ((obj=keywds.getKey("COLLABJUS")) != null) {
        String clabjus = Convert.o2s(obj);
        setColLabJus(clabjus);
      }
      if ((obj=keywds.getKey("INDEX")) != null) {
        String rlabs = Convert.o2s(obj);
        if (rlabs.equals("OFF")) setRowLabels(false); else setRowLabels(true);
      }
      if ((obj=keywds.getKey("FIELDS")) == null) return;
      int fields = Convert.o2l(obj);

      if ((obj=keywds.getKey("COLOR")) != null)  {
        defaultColor = Convert.o2s(obj);
      }
      else defaultColor = MW.theme.cfg.toString();

      //Read the format attributes for each field
      Table tmpTable = new Table();
      for (int k=1; k<=fields; k++) {
        if (keywds.setScope("FIELD="+k) == 1) { //found
          Table subrTable = new Table();
          if ((obj=keywds.getKey("FIELD")) != null)
            subrTable.put("FIELD", Integer.valueOf(Convert.o2s(obj)));
          subrObj = keywds.getKey("SUBR");
          if ((obj=keywds.getKey("LABEL")) != null)
            subrTable.put("LABEL", Convert.o2s(obj));
          if ((obj=keywds.getKey("COLOR")) != null)
            subrTable.put("COLOR", Convert.o2s(obj));
          if ((obj=keywds.getKey("WIDTH")) != null)
            subrTable.put("WIDTH",  Integer.valueOf(Convert.o2s(obj)));
          if ((obj=keywds.getKey("DISPFORM")) != null)
            subrTable.put("DISPFORM", Convert.o2s(obj));
          if ((obj=keywds.getKey("HIDE")) != null)
            subrTable.put("HIDE", Integer.valueOf(Convert.o2s(obj)));
          if ((obj=keywds.getKey("COLUMNTYPE")) != null)
            subrTable.put("COLUMNTYPE", Convert.o2s(obj));
          if ((obj=keywds.getKey("RASTERFRAME")) != null)
            subrTable.put("RASTERFRAME", Integer.valueOf(Convert.o2s(obj)));
          if ((obj=keywds.getKey("RASTERLEVELS")) != null)
            subrTable.put("RASTERLEVELS", Integer.valueOf(Convert.o2s(obj)));
          if ((obj=keywds.getKey("RASTERCOLORMAP")) != null)
            subrTable.put("RASTERCOLORMAP", Convert.o2s(obj));
          if ((obj=keywds.getKey("RASTERSELCOLOR")) != null)
            subrTable.put("RASTERSELCOLOR", Convert.o2s(obj));
          if ((obj=keywds.getKey("STRETCH")) != null)
            subrTable.put("STRETCH", Integer.valueOf(Convert.o2s(obj)));
          if ((obj=keywds.getKey("TIMEFMT")) != null)
            subrTable.put("TIMEFMT", Integer.valueOf(Convert.o2s(obj)));
          if ((obj=keywds.getKey("MENU")) != null)
            subrTable.put("MENU", Convert.o2s(obj));
          if ((obj=keywds.getKey("MVALUES")) != null)
            subrTable.put("MVALUES", Convert.o2s(obj));
          if ((obj=keywds.getKey("MCOLORS")) != null)
            subrTable.put("MCOLORS", Convert.o2s(obj));

          //Create a table of tables to store column info
          //Key value = subrecord name
          if (subrObj != null) tmpTable.put(Convert.o2s(subrObj), subrTable);
        }
      }

      int numCols = columns.size();
      boolean templateSetup = false;
      if (numCols==0) {
        String[] sa = tmpTable.getKeys();
        setColLabels(sa, sa.length, false);
        numCols = columns.size();
        templateSetup = true;
      }

      for (int i=0;i<numCols;i++) {
        Column gc = columns.elementAt(i);
        Table t = (Table)tmpTable.get(gc.subr);
        if (t != null) {
          gc.index = (Integer)t.get("FIELD");
          gc.field = Convert.o2l(t.get("FIELD"));
          if (templateSetup) gc.origIndex = gc.index.intValue()-1;
          gc.width = (((Integer)t.get("WIDTH")).intValue()+2)*charwidth;
          gc.origWidth = gc.width;
          gc.label = ((String)t.get("LABEL")).trim();

          //Optional template fields...
          if (t.get("COLUMNTYPE") != null) {
            String columnType = ((String)t.get("COLUMNTYPE")).trim();
            if (columnType.equals("PANEL")) gc.cellType = T_PANEL;
            else if (columnType.equals("TOGGLE")) gc.cellType = T_TOGGLE;
            else if (columnType.equals("RASTER")) gc.cellType = T_RASTER;
            else if (columnType.equals("TIME"))   {
              gc.cellType = T_TIME;
              if (t.get("TIMEFMT") != null) gc.timeFormat = ((Integer)t.get("TIMEFMT")).intValue();
              else gc.timeFormat = Time.STD;
            } else gc.cellType = T_STRING;
          }

          if (t.get("DISPFORM") != null)       gc.setFormatter(((String)t.get("DISPFORM")).trim());

          if (t.get("RASTERFRAME") != null)    gc.rasterFrame = ((Integer)t.get("RASTERFRAME")).intValue();
          if (t.get("RASTERLEVELS") != null)   gc.rasterLevels = ((Integer)t.get("RASTERLEVELS")).intValue();
          if (t.get("RASTERCOLORMAP") != null) gc.rasterColorMap = (String)t.get("RASTERCOLORMAP");
          if (t.get("RASTERSELCOLOR") != null) gc.rasterSelColor = (String)t.get("RASTERSELCOLOR");

          if (t.get("HIDE") != null)
            gc.visible = (((Integer)t.get("HIDE")).intValue() == 0);
          else gc.visible = true;

          if (t.get("STRETCH") != null)
            gc.stretch = (((Integer)t.get("STRETCH")).intValue() == 1);
          else gc.stretch = false;

        } else
          gc.visible = false;
      }

       for (int i=0;i<numCols;i++) {
        Column gc = columns.elementAt(i);
        Table t = (Table)tmpTable.get(gc.subr);
        if (t != null)  {
          if (t.get("MCOLORS") != null)  {
              String tempMenu = Convert.o2s(t.get("MENU"));
              String tempValues = Convert.o2s(t.get("MVALUES"));
              String tempColors = Convert.o2s(t.get("MCOLORS"));
              gc.setColorRule(new EnumColorRule(tempMenu, tempValues, tempColors, gc.origIndex));
          } else if (t.get("COLOR") != null)  {
            try  {
              if (Convert.o2s(t.get("COLOR")).substring(0,5).equalsIgnoreCase("FIELD"))  {
                String tempField = Convert.o2s(t.get("COLOR")).substring(5);
                gc.setColorRule(new TrackColorRule(Convert.o2l(tempField)));
              } else  {
                String tempIndex = Convert.o2s(t.get("COLOR"));
                gc.definedColor = MColor.getColor(tempIndex);
                gc.setColorRule(new ConstColorRule(MColor.getColor(tempIndex)));
              }
            } catch (StringIndexOutOfBoundsException e)  {
              String tempIndex = Convert.o2s(t.get("COLOR"));
              if (tempIndex.charAt(0) < "0".charAt(0) || tempIndex.charAt(0) > "9".charAt(0))  {
                gc.definedColor = MColor.getColor(tempIndex);
                gc.setColorRule(new ConstColorRule(MColor.getColor(tempIndex)));
              } else  {
                gc.setColorRule(new ConstColorRule(MColor.getColorByIndex(Convert.o2l(t.get("COLOR")))));
              }
            }
          } else  {
            try  {
              if (defaultColor.substring(0,5).equalsIgnoreCase("FIELD"))  {
                String tempField = defaultColor.substring(5);
                gc.setColorRule(new TrackColorRule(Convert.o2l(tempField)));
              } else  {
                gc.definedColor = MColor.getColor(defaultColor);
                gc.setColorRule(new ConstColorRule(MColor.getColor(defaultColor)));
              }
            } catch (StringIndexOutOfBoundsException e)  {
                if (defaultColor.charAt(0) < "0".charAt(0) || defaultColor.charAt(0) > "9".charAt(0))  {
                gc.definedColor = MColor.getColor(defaultColor);
                gc.setColorRule(new ConstColorRule(MColor.getColor(defaultColor)));
              } else  {
                gc.setColorRule(new ConstColorRule(MColor.getColorByIndex(Integer.decode(defaultColor).intValue())));
              }
            }
          }
        }
      }
      //Collections.sort(columns); qrefresh(); //BAH Oct03
      qrefresh();
  }

  /** Sort the data by column as selected via subrecord name.
   *  @param subrToSort subrecord to sort
   *  @param sortDesc   true if descending sort
   */
  public void sort(String subrToSort, boolean sortDesc) {
      boolean sortFound = false;

      for (int i=0; i<columns.size(); i++) {
        Column col = columns.elementAt(i);
        if (subrToSort.equals(col.subr)) {
          sortIndex = i;
          sortFound = true;
        }
      }

      if (sortFound) {
        this.sortDesc = sortDesc;
        Collections.sort(rows);
        renumberRows(true);
      }
  }

  /** Sort the data by column as selected via subrecord name. Each call
      will sort in the opposite direction in which it is currently sorted.
      @param subrToSort sub-record to sort
 */
 public void sort(String subrToSort) {
     sort(subrToSort, !this.sortDesc);
 }

 /** Sort the data by the column that it was sorted by last time. Each call
       will sort in the same direction in which it is currently sorted.
  */
  public void sort() {
      Column col = columns.elementAt(sortIndex);
      String subrToSort = col.subr;
      sort(subrToSort, this.sortDesc);
  }

  /** process selection
   *
   *  @param type    selection type
   *  @param selRow  selection row
   *  @param selCols columns selected
   */
  public void processSelection(String type, int selRow, int[] selCols) {
      Table t = new Table();
      t.put("TYPE", type);
      if (selectOne) t.put("SELECTONE",1); //MSZ ADD
      else t.put("SELECTONE",0);
      if (selectRange) t.put("SELECTRANGE",1);
      else t.put("SELECTRANGE",0); //MSZ ADD DONE
      if (type.equals("CELLS")) {
          t.put("ROW", Integer.valueOf(selRow+1));
          Row arow = rows.elementAt(selRow);
          for (int i=0; i<selCols.length; i++) {
            Column gc = columns.elementAt(selCols[i]);
            t.put(gc.subr, arow.cells[gc.origIndex].getData());
          }
      }
      MW.process("SELECT",0,t);
  }

  /** Process a selection

   The select message DATA for type ROW is now:
     TYPE      - ROW
     ROW       - 1..n row number
     COLUMN    - 1..n COLUMN number
     VALUE     - Table of row last clicked
     CURRSEL   - Table of Tables of selected rows/col data
                 For example, CURRSEL.3, CURRSEL.13
     PREVSEL   - Table of Tables of previously selected rows/col data
  @param type   - ROW, COL, CELL
  @param selRow - Row to select
  @param selCol - Column to select
  */
  public void processSelection(String type, int selRow, int selCol) {

      Object obj = null;
      boolean selected = true;
      Object prevRowsObj = getSelRowsTable(); // Collection (Table or Vector) of deselected rows
      Object selRowsObj  = null; // Collection (Table or Vector) of selected rows
      Table t = new Table();
      t.put("TYPE",type);
      //Note: selRow is zero-based
      if (type.equals("CELL")) {
          if (autoSelect) selectCell(selRow+1,selCol+1);
          t.put("ROW", Integer.valueOf(selRow+1));
          t.put("COLUMN", Integer.valueOf(selCol+1));
          Row arow = rows.elementAt(selRow);
          Column gc = columns.elementAt(selCol);
          if (gc.getColorRule() instanceof EnumColorRule) {
            EnumColorRule erule = (EnumColorRule)gc.getColorRule();
            gc.selRow = selRow+1;
            new GMenu(MW,gc.label,erule.menuItems,0,0,this);
            return;
          }
          t.put("VALUE",arow.cells[gc.origIndex].getData());
          t.put("NAME",gc.subr);
      } else if (type.equals("ROW")) {
          if (autoSelect) selectRow(selRow+1);
          t.put("ROW", Integer.valueOf(selRow+1));
          t.put("COLUMN", Integer.valueOf(-1));
          if (wayneMode && wayneDoubleClick) { t.put("DOUBLECLICK","TRUE"); }
          Row arow = rows.elementAt(selRow);
          selected = arow.selected;

          switch (outtype) {
            case T_TABLE:
              obj = (Object)arow.toTable();
              selRowsObj = new Table();
              if (!selectOne) {
                if (MultiSelMsgMode == MULTISEL_FULL) {
                  selRowsObj = getSelRowsTable();
                }
              }
              break;
            default:
              obj = (Object)arow.rowData;
              if (!selectOne) {
                if (MultiSelMsgMode == MULTISEL_FULL) {
                  selRowsObj = getSelRowsVector();
                }
              }
              break;
          }
          t.put("NAME", "");
          t.put("VALUE", obj);
          if (MultiSelMsgMode != MULTISEL_OFF) {
            t.put("CURRSEL", selRowsObj);
            t.put("PREVSEL", prevRowsObj);
          }
      } else if (type.equals("COLUMN")) {
          if (autoSelect) selectColumn(selCol+1);
          t.put("ROW", Integer.valueOf(-1));
          t.put("COLUMN", Integer.valueOf(selCol+1));
          Column gc = columns.elementAt(selCol);
          switch (outtype) {
            case T_TABLE:
              obj = (Object)gc.toTable(rows); break;
            default:
              obj = (Object)gc.toVector(rows); break;
          }
          t.put("VALUE", obj);
          t.put("NAME", gc.subr);
      }
      if (selectOne) t.put("SELECTONE",1); //MSZ ADD
      else t.put("SELECTONE",0);
      if (selectRange) t.put("SELECTRANGE",1);
      else t.put("SELECTRANGE",0); //MSZ ADD DONE

      if(!selectRange || selected) {
          MW.process("SELECT",0,t);
      }
  }

  //*****************************************************************************
  //Implement Keyable interface methods
  public String[] getKeys() { return null; }

  public Object setKey (String name, Object value) {
      if (name.equals("SELECTROW")) selectRow(Convert.o2l(value));
      //else if (name.equals("SELECTCOLUMN")) selectColumn(Convert.o2l(value), true);
      else if (name.equals("SCROLLEND")) scrollEnd = scrollEndSw;
      else return null;
      return value;
  }

  public Object getKey (String name) {
      if (name.startsWith("DATA")) return rows;
      else if (name.equals("SELECTEDROW")) return Convert.l2o(getSelectedRow());
      else if (name.equals("SELECTEDCOLUMN")) return Convert.l2o(getSelectedColumn());
      return null;
  }

  //*****************************************************************************
  // Listeners/Event Handlers
  //*****************************************************************************
  class GridComponentAdapter extends java.awt.event.ComponentAdapter {
    public void componentResized(java.awt.event.ComponentEvent event) {
      Object object = event.getSource();
      if (object == GridSheet.this) {
        setSize(MW.pos.w,MW.pos.h);
        g.setClip(0,0,MW.pos.w,MW.pos.h);
        qrefresh();
      }
    }
  }

  //*******************************************************
  class VScrollAdjustmentListener implements java.awt.event.AdjustmentListener {
      public void adjustmentValueChanged(java.awt.event.AdjustmentEvent event)
      {
          try {
              int atype = event.getAdjustmentType();
              if (atype==AdjustmentEvent.BLOCK_DECREMENT) { startrow-=vblockincrement; if (startrow<0) startrow=0; }
              else if (atype==AdjustmentEvent.BLOCK_INCREMENT) { startrow+=vblockincrement; if (startrow>=currows) startrow = currows-1; }
              else if (atype==AdjustmentEvent.UNIT_DECREMENT) { startrow--; if (startrow<0) startrow=0; }
              else if (atype==AdjustmentEvent.UNIT_INCREMENT) { startrow++; if (startrow>currows) startrow = currows-1; }
              else if (atype==AdjustmentEvent.TRACK) {
                int slidevalue = event.getValue();
                startrow = slidevalue;
                if (startrow<0) startrow=0;
                if (startrow>currows) startrow = currows-1;
              }
              qrefresh();
          } catch (Exception e) {
              System.out.println("Exception in VScroll listener:"+e);
          }
      }
  }

  //*******************************************************
  class HScrollAdjustmentListener implements java.awt.event.AdjustmentListener {
      public void adjustmentValueChanged(java.awt.event.AdjustmentEvent event)
      {
          try {
              int atype = event.getAdjustmentType();
              if (atype==AdjustmentEvent.BLOCK_DECREMENT) { startcol-=hblockincrement; if (startcol<0) startcol=0; }
              else if (atype==AdjustmentEvent.BLOCK_INCREMENT) { startcol+=hblockincrement; if (startcol>curcols) startcol=curcols-1; }
              else if (atype==AdjustmentEvent.UNIT_DECREMENT) { startcol--; if (startcol<0) startcol=0; }
              else if (atype==AdjustmentEvent.UNIT_INCREMENT) { startcol++; if (startcol>curcols) startcol=curcols-1; }
              else if (atype==AdjustmentEvent.TRACK) {
                int slidevalue = event.getValue();
                if (startcol != slidevalue) startcol = slidevalue;
                if (startcol>curcols) startcol=curcols-1;
                if (startcol>curcols) startcol=curcols-1;
              }
              qrefresh();
          } catch (Exception e) {
              System.out.println("Exception in HScroll listener:"+e);
          }
      }
  }

  //*******************************************************
  //DataStructures!!
  //*******************************************************
  /** An inner class for a Row within a data set */
  public class Row implements Comparable<Object> {
      Vector<Object> rowData;
      CellRenderer[] cells;
      int count;
      boolean selected;
      String label;

      /** Row Constructor
          @param rowSize Row Size
       */
      public Row(int rowSize) {
          rowData = new Vector<>();
          selected=false;
          count = rowSize;
          cells = new CellRenderer[rowSize];
      }

      /** Returns a NeXtMidas Table representation of the Row
          @return NeXtMidas Table of the Row
       */
      public Table toTable() {
          Table t  = new Table();
          for (int i=0; i<count; i++) {
            Column gc = columns.elementAt(i);
            t.put(gc.subr.toUpperCase(), (Object)cells[gc.origIndex].getData());
          }
          return t;
      }

      /** Sets the cells of the Row
          @param cells new cells for the Row
       */
      public void setCells (CellRenderer[] cells) { this.cells = cells; }
      /** Gets the cells of the Row
          @return cells of the Row
       */
      public CellRenderer[] getCells () { return cells; }

      /**
       * This method relies on sortDesc and sortIndex class variables.
       */
      public int compareTo (Object o) {
        Object A = rowData.elementAt(sortIndex);
        Object B = (((Row)o).rowData).elementAt(sortIndex);
        Object X,Y;
        if (sortDesc) { //Descending
          X=B;Y=A;
        } else {        //Default ascending
          X=A;Y=B;
        }
        if (B.getClass().isInstance(A)) {
          if (A instanceof Double)       return ((Double)X).compareTo((Double)Y);
          else if (A instanceof Float)   return ((Float)X).compareTo((Float)Y);
          else if (A instanceof Long)    return ((Long)X).compareTo((Long)Y);
          else if (A instanceof Integer) return ((Integer)X).compareTo((Integer)Y);
          else if (A instanceof Byte)    return ((Byte)X).compareTo((Byte)Y);
          else return X.toString().compareTo((String)Y);
        }
        return 0;
      }
  }

  //*******************************************************
  /** cell renderer class */
  public interface CellRenderer {
    /** get Data
     *  @return String Data
     */
    public String getData();
    /** set Data
     *  @param val String Data */
    public void setData(String val);
    /** set rectangle
     *
     *  @param x      column position
     *  @param y      row position
     *  @param width  width
     *  @param height height
     */
    public void setRectangle(int x, int y, int width, int height);
    /** set max X
     *  @param maxX maximim x
     */
    public void setMaxX(int maxX);
    /** set colors
     *
     *  @param fg foreground color
     *  @param bg background color
     */
    public void setColors(Color fg, Color bg);
    /** set colors */
    public void setColors();
    /** set selected attributes
     *
     *  @param fgHilite foreground highlight
     *  @param selcolor selection color
     */
    public void setSelected(boolean fgHilite, Color selcolor);
    /** set selected attributes
     *
     *  @param fgHilite foreground highlight
     *  @param selcolor selection color
     *  @param fg       foreground color
     *  @param bg       background color
     */
    public void setSelected(boolean fgHilite, Color selcolor, Color fg, Color bg);
    /** set whether this cell is selected
     *  @param val true for selected
     */
    public void setSelected(boolean val);
    /** is this selected
     *  @return true if selected
     */
    public boolean isSelected();
    /** is this in XBounds
     *  @param xval x value to check
     *  @return true fi in bounds
     */
    public boolean inXBounds(int xval);
    /** refresh */
    public void refresh();
    /** Add to MWindow
     *  @param mw MWindow to add this to
     */
    public void addTo(MWindow mw);
    /** hide panel */
    public void hidePanel();
    /** show panel */
    public void showPanel();
    /** toggle */
    public void toggle();
    /** Get Data Object
     *  @return Data Object
     */
    public Object getDataObj();
    /** set Data object
     *  @param obj Data Object
     */
    public void setDataObj(Object obj);
    /** get default foreground color
     *  @return foreground color */
    public Color getDefFg();
    /** get default background color
     *  @return background color */
    public Color getDefBg();
}

//*******************************************************
  /** Abstract Class Abstract Cell Renderer */
public abstract class AbstractCellRenderer extends Object implements CellRenderer {
    String data;
    Color defFg, forecolor;
    Color defBg, backcolor;
    boolean selected;
    int   minX, maxX, Y;
    int   width, height;
    int   fmt = 0;

    /** default constructor */
    public AbstractCellRenderer () {}

    public void refresh() {}
    public void hidePanel() {}
    public void showPanel() {}
    public void toggle() {}
    public void setDataObj(Object obj) {}
    public Object getDataObj() { return null; }

    public void addTo(MWindow mw) {}
    public void setData(String val) { data = val; }
    public String getData() { return data; }
    public void setSelected(boolean val) { selected = val; }
    public boolean isSelected() { return selected; }
    public Color getDefFg() { return defFg; }
    public Color getDefBg() { return defBg; }

    public void setColors() {
      forecolor = defFg;
      backcolor = defBg;
    }
    public void setColors(Color fg, Color bg) {
      forecolor = fg;
      backcolor = bg;
    }
    public void setRectangle(int x, int y, int width, int height) {
      this.minX = x;
      this.Y = y;
      this.width = width;
      this.height = height;
    }
    /** Set string format
     *  @param fmt int defining format
     */
    public void setStringFmt(int fmt) { this.fmt = fmt; }
    public void setMaxX(int maxX) { this.maxX = maxX; }
    public boolean inXBounds(int xval) {
      if ((xval>=minX) && (xval<=maxX)) return true;
      else return false;
    }
    public void setSelected(boolean fgHilite, Color selcolor) {
      if (fgHilite) {
        forecolor = selcolor;
        backcolor = defBg;
      } else {
        forecolor = defFg;
        backcolor = selcolor;
      }
    }
    public void setSelected(boolean fgHilite, Color selcolor, Color fg, Color bg) {
      if (fgHilite) {
        backcolor = bg;
        forecolor = selcolor;
      } else {
        forecolor = fg;
        backcolor = selcolor;
      }
    }
  }

  //*******************************************************
  /** cell panel class */
  public class CellPanel extends AbstractCellRenderer {
    MPanel cellPanel;
    GridSheet GS;

    /** Cell Panel constructor
     *
     *  @param mw      MWindow
     *  @param gs      GridSheet
     *  @param defFore default foreground Color
     * @param defBack  default background Color
     */
    public CellPanel(MWindow mw, GridSheet gs, Color defFore, Color defBack) {
      cellPanel = new MPanel();
      cellPanel.setForeground(defFore);
      cellPanel.setBackground(defBack);
      GS = gs;
      addTo(mw);
    }

    public void addTo(MWindow mw) {
      cellPanel.MW = mw;
      GS.add(cellPanel);
    }

    public void hidePanel() { cellPanel.setVisible(false); }
    public void showPanel() { cellPanel.setVisible(true); }

    public Object getDataObj() { return (Object)cellPanel; }
    public void setDataObj(Object obj) { cellPanel = (MPanel)obj; }

    public void refresh() {
      if(DEBUG_REFRESH) { System.out.println("  CellPanel.refresh()"); }
      cellPanel.setBounds(minX,Y-height+1,width-1,height-1);
      cellPanel.setBackground(backcolor);
    }
  }

  //*******************************************************
  /** Cell String */
  public class CellString extends AbstractCellRenderer {
    double tmp;
    int xoff=5;

    /** CellString Constructor
     *
     *  @param defFore default foreground color
     *  @param defBack default background color
     *  @param fmt     format
     */
    public CellString(Color defFore, Color defBack, int fmt) {
      defFg = defFore; defBg = defBack;
      forecolor = defFore;
      backcolor = defBack;
      selected  = false;
      minX = 0;
      maxX = 0;
      this.fmt = fmt;
    }

    /** CellString Constructor
     *
     *   @param defFore default foreground color
     *   @param defBack default background color
     */
    public CellString(Color defFore, Color defBack) {
      defFg = defFore; defBg = defBack;
      forecolor = defFore;
      backcolor = defBack;
      selected  = false;
      minX = 0;
      maxX = 0;
    }

    public Object getDataObj() { return (Object)data; }
    public void setDataObj(Object obj) { data = Convert.o2s(obj); }

    public void setColors(Color fg, Color bg)  {
      defFg = fg;
      defBg = bg;
      forecolor = fg;
      backcolor = bg;
      if(JC4_IMPROVED_DRAW) {
        needRefresh = true;
      } else {
        refresh();
      }
    }

    public void refresh() {
      if(DEBUG_REFRESH) { System.out.println("  CellString.refresh()"); }
      g.setColor(backcolor);
      g.fillRect(minX,Y-height+1,width-1,height-1);
      g.setColor(forecolor);
      if (fmt > 0) {
        tmp = Convert.s2d(data);
        g.drawString(Time.format(tmp,fmt,-1),minX+xoff,Y-2); // 4.0.1 Jeff xoff
      }
      else {
        g.drawString(data,minX+xoff,Y-2); // 4.0.1 Jeff xoff
      }
    }
  }

  //*******************************************************
  /** Cell Toggle */
  public class CellToggle extends AbstractCellRenderer {
    /** "on" value */
    public String onVal  = "ON";
    /** "off" value */
    public String offVal = "";
    private boolean state = false; // JC4: Changed to private as per JPH/MSZ

    /** CellToggle Constructor
        @param defFore foreground Color
        @param defBack background Color
     */
    public CellToggle(Color defFore, Color defBack) {
      defFg = defFore; defBg = defBack;
      forecolor = defFore;
      backcolor = defBack;
      selected  = false;
      minX = 0;
      maxX = 0;
    }

    public Object getDataObj() { return (Object)data; }
    public void setDataObj(Object obj) { state = Convert.o2s(obj).equals(onVal); }

    public void toggle() {
      state = !state;

      refresh(); // Keep this refresh() -- removing it caused problems for MSZ
    }

    public void refresh() {
      if(DEBUG_REFRESH) { System.out.println("  CellToggle.refresh()"); }
      int mode;
      if (state)
        mode = -1;
      else
        mode = 0;

      g.setColor(forecolor);
      int tmp = (width - height)/2;
      if (state) {
        data = onVal;
        g.fill3DRect(minX+tmp,Y-height+5,width-(tmp*2),height-10,true);
      } else {
        data = offVal;
        shadowbox(minX+tmp,Y-height+5,width-(tmp*2),height-10,-1);
      }
      //Label the button
      //if (state) data = onVal; else data = offVal;
      //g.drawString(data,minX+10,Y-2);
    }

  }

  //*******************************************************
  /** Cell Raster */
  public class CellRaster extends AbstractCellRenderer {
    MWindow MW;
    CImage cimage;
    Color[] colors;
    boolean newdata = false;
    int size   = 1;
    int levels = 16;
    int frame  = 16;
    String colormap = "RAMP";
    Rectangle r;
    int rindex  = -1;
    int rindexDisp = -1;
    boolean enableSel = false;
    boolean visible = true;
    Color selColor = Color.green;
    byte selByteVal = 0;
    byte saveBytes[] = new byte[3];
    String label = "";

    /** current buffer */
    public byte[] currBuf;
    /** time buffer */
    public double[] timeBuf;
    /** time objects */
    public Vector<Object> timeObj;

    /** CellRaster Constructor
        @param mw Midas Graphical Window
     */
    public CellRaster(MWindow mw) {
      this.MW   = mw;
      selected  = false;
      minX      = 0;
      maxX      = 0;
    }

    /** Cell Raster constructor
     *
     *  @param mw      MWindow
     *  @param frame   frame width
     *  @param levels   levels (number of colors)
     *  @param colormap String colormap
     *  @param selColor selection color
     */
    public CellRaster(MWindow mw, int frame, int levels, String colormap, String selColor) {
      this.frame     = frame;
      this.levels    = levels;
      this.colormap  = colormap;
      this.selColor  = MColor.getColor(selColor);
      this.MW   = mw;
      selected  = false;
      minX      = 0;
      maxX      = 0;
      r = new Rectangle();
      currBuf = new byte[frame];
      timeBuf = new double[frame];
      timeObj = new Vector<>(frame,10);
      setData(currBuf);
    }

    /** Returns the current CellRaster instance casted as a generic Java Object
        @return the current CellRaster instance casted as a generic Java Object
     */
    public Object getDataObj() { return (Object)this; }
    public void setDataObj(Object obj) { byte[] bptr = Convert.o2ba(obj); setData(bptr); }

    /** set Data
     *  @param buf byte buffer containing data
     */
    public void setData(byte[] buf) {
      if (cimage==null) {
        currBuf = buf;
        Color[] tmpcolors = MColor.getColorMap(colormap,levels);
        int plusOne = tmpcolors.length+1;
        colors = new Color[plusOne];
        System.arraycopy(tmpcolors,0,colors,0,tmpcolors.length);
        colors[plusOne-1] = selColor;
        selByteVal = (byte)(plusOne-1);
        cimage = new CImage(g,frame,size,buf,colors);
        cimage.setColors(colors);
      } else {
        cimage.source.newPixels(1,1,frame,1);
        cimage.image.flush();
        cimage.setColors(colors);
      }
    }

    /** Get data dsiplayed and set label
     *
     *  @param columnLabel label
     *  @return data displayed
     */
    public String getData(String columnLabel) {
      this.label = columnLabel;
      return getDataDisp();
    }

    /** Get data displayed
     *  @return data displayed
     */
    public String getDataDisp() {
        String tmp = "";
        if (rindexDisp>-1) {
          String tmpT=null, tmpC=null, tmpD=null;
          tmpT = Time.format(timeBuf[rindexDisp],Time.STD);
          tmpT = StringUtil.trim(tmpT,"::",""); // 3.9.x switched to StringUtil
          tmpC = Time.format(timeBuf[0],Time.STD);
          tmpC = StringUtil.trim(tmpC,"::",""); // 3.9.x switched to StringUtil
          tmpD = Time.format((timeBuf[0]-timeBuf[rindexDisp]),Time.STD);
          tmpD = StringUtil.trim(tmpD,"::",""); // 3.9.x switched to StringUtil
          tmp = "(current="+tmpC+" "+label+"="+tmpT+" delta="+tmpD+")";
        } else
          tmp=""+rindexDisp;

        return tmp;
    }

    /** get frame width
     *  @return frame width
     */
    public int getFrame(){return frame;}
    /** Get Time Vector
     *  @return time object Vector
     */
    public Vector<Object> getTimeVector() {
      return timeObj;
    }

    /** get time object by index
     *
     *  @param index index of time object
     *  @return time object at that index
     */
    public Object getTimeObject(int index) {
      try {
        if (index>=0 && index<timeObj.size())
          return timeObj.get(index);
        else
          return null;
      } catch (Exception e) {
        System.out.println("Exception in getTimeObject:"+e);
        return null;
      }
    }

    /** Set time object at index
     *
     *  @param index index to set
     *  @param obj   object
     *  @return the object preciously at that index
     */
    public Object setTimeObject(int index,Object obj) {
      try {
        if (index>=0 && index<timeObj.size())
          return timeObj.set(index,obj);
        else
          return null;
      } catch (Exception e) {
        System.out.println("Exception in setTimeObject:"+e);
        return null;
      }
    }

    /** Add time object
     *
     *  @param obj object to add
     *  @return true if added
     */
    public boolean addTimeObject(Object obj) {
      try {
        return timeObj.add(obj);
      } catch (Exception e) {
        System.out.println("Exception in addTimeObject:"+e);
        return false;
      }
    }

    /** Remove time object by index
     *
     *  @param index index of time object to be removed
     *  @return removed object
     */
    public Object removeTimeObject(int index) {
      try {
        return timeObj.remove(index);
      } catch (Exception e) {
        System.out.println("Exception in addTimeObject:"+e);
        return null;
      }
    }

    /** Adds object to time object and removes last entry if
     *  time object size is greater than or equal to frame width
     *  @param obj object to add
     *  @return true if added
     */
    public boolean shiftTimeObject(Object obj) {
//removes last Object from Vector and adds a new one if Vector size has been reached
      try {
        if (timeObj.size()>=frame) {
          timeObj.remove(0);
          return timeObj.add(obj);
        } else {
          return timeObj.add(obj);
        }
      } catch (Exception e) {
        System.out.println("Exception in shiftTimeObject:"+e);
        return false;
      }
    }

    /** Get time difference between latest and to index
     *
     *  @param latestindex latest time buffer index
     *  @param toindex     back to this time buffer index
     *  @return time difference
     */
    public double getTimeDiff(int latestindex,int toindex) {
      if (latestindex >= 0 && toindex>=0 && latestindex<=frame && toindex<=frame)
        return timeBuf[latestindex]-timeBuf[toindex];
      else
        return 0.0;
    }
    /** Get time difference
     *
     *  @param index index into time buffer
     *  @return time difference as double
     */
    public double getTimeDiff(int index) {
      if (index >= 0)
        return timeBuf[0]-timeBuf[index];
      else
        return 0.0;
    }

    public String getData() { return ""+rindex; }

    /** set the displayed index based on Point
     *
     *  @param p Point to check against
     *  @return true if found and displayed index is set
     */
    public boolean getValueDisp(Point p) {
      boolean found = false;
      if (r.contains(p)) {
        double rw = (double)r.width/(double)frame;
        int i = (int)((p.x-r.x)/rw);
        rindexDisp = i;
      }
      return found;
    }

    /** set index if Point is contained in Cell Raster Rectangle
     *
     *  @param p Point
     *  @return true if Point is contained in this Cell Raster
     */
    public boolean getValue(Point p) {
      boolean found = false;
      if (r.contains(p)) {
        double rw = (double)r.width/(double)frame;
        int i = (int)((p.x-r.x)/rw);
        rindex = i;
      }
      return found;
    }

    /** Enable raster selection */
    public void enableRasterSelect() { enableSel = true;}
    /** Clears raster selection */
    public void clearRasterSelect() {
      rindex = -1;
      enableSel = false;

      if(JC4_IMPROVED_DRAW) {
        needRefresh = true;
      } else {
        refresh();
      }
    }

    public void hidePanel() { visible = false;}
    public void showPanel() { visible = true;}

    /** New Data
     *  @param tmp not used
     */
    public void newData(int tmp) {
      if (enableSel && rindex>-1) {
        if (rindex>currBuf.length-2) rindex = currBuf.length-2;
        System.arraycopy(currBuf,rindex-1,saveBytes,0,3);
        for (int i=rindex-1; i<=rindex+1; i++) currBuf[i] = selByteVal;
      }
      cimage.source.newPixels(1,1,frame,1);
      cimage.image.flush();
      cimage.setColors(colors);
      if (enableSel && rindex>0) System.arraycopy(saveBytes,0,currBuf,rindex-1,3);

      if(JC4_IMPROVED_DRAW) {
        needRefresh = true;
      } else {
        refresh();
      }
    }

    public void refresh() {
      if(DEBUG_REFRESH) { System.out.println("  CellRaster.refresh()"); }
      r.setRect(minX, Y-height+1, width-1, height-1);
      if (cimage != null && visible) {
        g.drawImage(cimage.image, r.x, r.y, r.width, r.height, cimage);
        needRefresh = true;
      }
      processMouseMove(MW.px, MW.py);
    }

    /** An inner-class of {@link GridSheet.CellRaster} that manages Images with Midas colors */
    public class CImage extends Object implements ImageObserver {
      /** The image                                      */ public  Image             image;
      /** Image ColorModel, see setColors(..)            */ public  ColorModel        colormodel;
      /** image Source                                   */ public  MemoryImageSource source;
      /** image width                                    */ public  int               w;
      /** image height                                   */ public  int               h;
      /** Graphics Context used                          */ private Graphics          g;
      /** array of pixels (uses colormodel for colormap) */ private byte[]            buf;

      /** CImage Constructor
          @param g Graphics context used
          @param width width of the Image
          @param height height of the Image
          @param buf the array of pixels for the Image
          @param colors colors for the Image's color model
       */
      public CImage (Graphics g, int width, int height, byte[] buf, Color[] colors) {
        this.g=g; this.w=width; this.h=height;
        this.buf=buf;
        setColors(colors);
        source = new MemoryImageSource(w,h,colormodel,buf,0,w);
        source.setAnimated(true);
        image = Toolkit.getDefaultToolkit().createImage(source);
      }

      /** Sets the Image's color model based on an Array of NeXtMidas colors
          @param colors Array of Midas Colors
       */
      public void setColors (Color[] colors) {
        colormodel=null;
        colormodel = MColor.getColorModel(colors);
        if (source!=null) source.newPixels(buf,colormodel,0,w);
      }

      /** Calls {@link Graphics#drawImage(Image, int, int, int, int, int, int, int, int, ImageObserver)}
          to draw a specified area of the image
          @param g   Graphics context used
          @param dx1 first x-coordinate of the destination
          @param dy1 first y-coordinate of the destination
          @param dx2 second x-coordinate of the destination
          @param dy2 second y-coordinate of the destination
          @param sx1 first x-coordinate of the source
          @param sy1 first y-coordinate of the source
          @param sx2 second x-coordinate of the source
          @param sy2 second y-coordinate of the source
       */
      public void draw (Graphics g,int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2) {
        g.drawImage(image, dx1,dy1,dx2,dy2, sx1,sy1,sx2,sy2, this);
      }

      /** Updates a given image, TODO: needs to be implemented
          @param img Image to be updated
          @param infoflags Image info flags (refer to {@link ImageObserver#imageUpdate(Image, int, int, int, int, int)})
          @param x x-coordinate for the Image
          @param y y-coordinate for the Image
          @param width width for the Image
          @param height height for the Image
       */
      public boolean imageUpdate (Image img, int infoflags, int x, int y, int width, int height) { return true; }
    }
  }

  //*******************************************************
  //** TBD
  //public class CellLine extends AbstractCellRenderer {}

  //*******************************************************
  /** An inner class for a Column within a data set */
  public class Column implements Comparable<Object> {
      Integer index;
      int origIndex;
      int width;
      int origWidth = 0;
      int cellType;
      int timeFormat;
      String label;
      String subr;
      boolean stretch = false;
      boolean selected;
      boolean visible = true;
      /** Column Decimal Format */
      public DecimalFormat nf;
      int rasterFrame  = 16;
      int rasterLevels = 16;
      String rasterSelColor = "GREEN";
      String rasterColorMap = "MULTI";
      Vector<Object> colData = new Vector<>();
      Color definedColor = null;
      ColorRule Crule;
      int field;
      int selRow;
      boolean dms = false;

      /** default constructor */
      public Column () {}

      /** Returns a NeXtMidas Table representation of the Column
          @param vov Vector of Rows in which to read data from the same column
          @return NeXtMidas Table representation of the Column
       */
      public Table toTable(Vector<Row> vov) {
          Table t  = new Table();
          int numRows = vov.size();
          for (int i=0; i<numRows; i++) {
            Row row = vov.elementAt(i);
            Integer tmp = Integer.valueOf(i+1);
            t.put(tmp.toString(), row.rowData.elementAt(origIndex));
          }
          return t;
      }

      /** Returns a Vector representation of the Column
          @param vov Vector of Rows in which to read data from the same column
          @return Vector representation of the Column*/
      public Vector<Object> toVector(Vector<Row> vov) {
          colData.clear();
          int numRows = vov.size();
          for (int i=0; i<numRows; i++) {
            Row row = vov.elementAt(i);
            colData.addElement(row.rowData.elementAt(origIndex));
          }
          return colData;
      }

      /** Compares the indices of two Columns
          @param o Object to compare index with (assumed to be Column)
          @return 0 - indices are equal, 1 if index of current Column index is less
                  than o index, and -1 if index of current Column index is less
                  than o index
       */
      public int compareTo (Object o) {
        try {
          Integer A = index;
          Integer B = ((Column)o).index;
          return A.compareTo(B);
        } catch(Exception e) { System.out.println("Error in compareTo: "+e); e.printStackTrace(); }
        return 0;
      }

      /** Gets the Color Rule
          @return Color Rule
       */
      public ColorRule getColorRule()  {
          return Crule;
      }

      /** Sets the Color Rule
          @param cr new Color Rule
       */
      public void setColorRule (ColorRule cr)  {
          Crule = cr;
      }

    /**
      Sets the DecimalFormat to be used for this column, if one is needed.
      @param pattern the java DecimalFormat pattern used to format this column.
      (See java.text.DecimalFormat javadocs for pattern syntax.)
     */
     public void setFormatter(String pattern) {
       //ignore legacy DISPFORM options
       if (pattern == null ||
           pattern.trim().equals("") ||        //general format
           pattern.startsWith("(") ||          //fortran format
           pattern.startsWith("[") ||          //c format
           pattern.equalsIgnoreCase("ENUM") || //enumerated format
           pattern.equalsIgnoreCase("GEN") ||  //general format
           pattern.equalsIgnoreCase("SCI") ||  //scientific format
           pattern.equalsIgnoreCase("ENG") ||  //engineering format
           pattern.equalsIgnoreCase("MAN") ||  //mantissa format
           pattern.equalsIgnoreCase("VIS") ||  //visual format
           //pattern.equalsIgnoreCase("DMS") ||  //degrees'minutes'seconds format
           pattern.equalsIgnoreCase("HMS") ) { //hours:minutes:seconds format
             Shell.getSharedMidasContext().warning("Invalid value for DISPFORM template keyword: " + pattern);
             return;
           }
       if (pattern.trim().equalsIgnoreCase("DMS")) {
         dms = true;
         return;
       }

       nf = new DecimalFormat(pattern);
     }

     /**
       Format the given data using the DecimalFormat for this column,
       if one has been set.
       @see #setFormatter(String)
       @param data data to format
       @return formated data
      */
     public String format(double data) {
       if (nf == null) {
//           if (dms == true) return Convert.deg2dms(data);
         if (dms == true) return Convert.deg2dms(data,"00.00");
         else return (Double.valueOf(data)).toString();
       }
       else return nf.format(data);
     }
  }

  /** Color Rule */
  public interface ColorRule {
      /** Get Color
       *
       *  @param rule List with rule
       *  @return Color
       */
      public Color getColor (List<?> rule);

      /** Get Data
       *
       *  @param s String defining the Data to get
       *  @return the data
       */
      public String getData (String s);
  }

  /** Abstract Class Abstract Color Rule */
  public abstract class AbstractColorRule extends Object implements ColorRule {

    /** default constructor */
    public AbstractColorRule () {}

    @Override
    public Color getColor(List<?> rule) { return null; }
    @Override
    public String getData(String s)     { return s;    }  //BAH
  }

  /** Constant Color Rule */
  public class ConstColorRule extends AbstractColorRule {
      Color myColor;

      /** ConstColorRule Constructor
       *  @param c Color for constant color rule
       */
      public ConstColorRule (Color c)      { myColor = c;    }
      public Color getColor (List<?> rule) { return myColor; }
  }

  /** Track Color Rule */
  public class TrackColorRule extends AbstractColorRule  {
      int ColumnToFollow;

      /** TrackColurRule Constructor
          @param ctf column to follow
       */
      public TrackColorRule (int ctf) {
          ColumnToFollow = ctf;
      }

      public Color getColor (List<?> rule)  {
       try {
        for (int i=0;i<columns.size();i++)  {
          Column gc = columns.elementAt(i);
          if (gc.field == ColumnToFollow) return gc.getColorRule().getColor(rule);
        }
       } catch(Exception e) { System.out.println("Error in TrackColorRule::getColor : "+e); e.printStackTrace(); }
        return null;
      }
  }

  /** enum color rule */
  public class EnumColorRule extends AbstractColorRule  {
      Table dataTable = new Table();
      Table menuTable = new Table();
      Table colorTable = new Table();
      String menuItems = "";
      int colToUse;

      /** EnumColorRule constructor
       *
       *  @param menu    menu
       *  @param mvalues menu values
       *  @param mcolors menu colors
       *  @param field   color to use
       */
      public EnumColorRule (String menu, String mvalues, String mcolors, int field)  {
       try {
          colToUse = field;
          menuItems = menu;

          StringTokenizer st1 = new StringTokenizer(menu,delimiters);
          StringTokenizer st2 = new StringTokenizer(mvalues,delimiters);
          StringTokenizer st3 = new StringTokenizer(mcolors,delimiters);
          String s1,s2,s3;
          while (st1.hasMoreTokens())  {
              s1 = st1.nextToken();
              s2 = st2.nextToken();
              s3 = st3.nextToken();
              if (s1.indexOf(".") != -1)  {
                s1 = s1.substring(0,s1.indexOf("."));
              }
              if (s2.indexOf(".") != -1)  {
                s2 = s2.substring(0,s2.indexOf("."));
              }
              menuTable.put(s1,s2);
              dataTable.put(s2,s1);
              if ((s3.length()>5) && (s3.substring(0,5).equalsIgnoreCase("FIELD")))  {
                String tempField = s3.substring(5);
                colorTable.put(s2,new TrackColorRule(Convert.o2l(tempField)));
              } else
                colorTable.put(s2,s3);
          }
        } catch (Exception e)  { System.out.println("Error in EnumColorRule::contructor : "+e); e.printStackTrace(); }
      }

      public Color getColor (List<?> rule)  {
          try  {
            String tempKey = null;
            String tempData = Convert.o2s(rule.get(colToUse));
            if (tempData.indexOf(".") != -1) tempData = tempData.substring(0,tempData.indexOf("."));

            if (menuTable.getKey(tempData) != null) tempKey = Convert.o2s(menuTable.getKey(tempData));
            else if (dataTable.getKey(tempData) != null) tempKey = tempData;
            else tempKey = null;

            String tempIndex = null;
            if (tempKey != null) {
              if (colorTable.getKey(tempKey) instanceof TrackColorRule) {
                TrackColorRule newTCR = (TrackColorRule)colorTable.getKey(tempKey);
                return newTCR.getColor(rule);
              } else {
                tempIndex = Convert.o2s(colorTable.getKey(tempKey)).toLowerCase();
                if (tempIndex != null) {
                  if (tempIndex.charAt(0) < "0".charAt(0) || tempIndex.charAt(0) > "9".charAt(0)) {
                    return MColor.getColor(tempIndex);
                  }
                }
              }
              return MColor.getColorByIndex(Convert.o2l(colorTable.getKey(tempKey)));
            } else  return Color.white;
          } catch (Exception e)  { System.out.println("Error in EnumColorRule::getColor : "+e); e.printStackTrace(); }
          return null;
      }

      public String getData (String s)  {
        try {
          if (s.indexOf(".") != -1) s = s.substring(0,s.indexOf("."));
          if (dataTable.getData(s) != null) //BAH Oct03
            return Convert.o2s(dataTable.getData(s));
          else return s;  //BAH Oct03 Assumes string is the value and not an index into a table
        } catch (Exception e) { System.out.println("Error in EnumColorRule::getData: "+e); e.printStackTrace(); return null; }
      }

  }
  /** An inner class that deals with events which involve DropTarget entities */
  public class StringDropTarget implements DropTargetListener {
     Component comp;
     DropTarget dt;
     Method dm;
     String str;

     /** StringDropTarget Constructor
         @param comp       target Component
         @param methodname name of method in target Component
         @param args       parameter types of method
      */
     public StringDropTarget(Component comp,String methodname,Class<?>[] args) {
       try {
         dm = comp.getClass().getMethod(methodname,args);
       } catch (Exception e) {
         e.printStackTrace();
       }
       this.comp=comp;
       dt=new DropTarget(comp,DnDConstants.ACTION_COPY_OR_MOVE,this);
     }

     public void dragEnter(DropTargetDragEvent e) {
       e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
     }

     public void drop(DropTargetDropEvent dtde) {
       try {
         Transferable tr = dtde.getTransferable();
         if (tr.isDataFlavorSupported(DataFlavor.stringFlavor)) {
           dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
           str=(String)tr.getTransferData(DataFlavor.stringFlavor);
           dm.invoke(comp,new Object[]{str});
           dtde.getDropTargetContext().dropComplete(true);
         }
       } catch (Exception e) {
         e.printStackTrace();
       }
     }
     public void dragExit(DropTargetEvent e) { }
     public void dragOver(DropTargetDragEvent e) { }
     public void dropActionChanged(DropTargetDragEvent e) { }
  }

  /** An inner class that deals with events which involve DragSource entities */
  public class StringDragSource implements DragSourceListener, DragGestureListener {
     Component comp;
     DragSource ds;
     DragGestureRecognizer dgr;
     Method dm;

     /** StringDragSource Constructor
         @param comp       source Component
         @param methodname name of method in source Component
         @param args       parameter types of method
      */
     public StringDragSource(Component comp,String methodname,Class<?>[] args) {
       try {
         dm = comp.getClass().getMethod(methodname,args);
       } catch (Exception e) {
         e.printStackTrace();
       }
       this.comp=comp;
       ds=new DragSource();
       dgr=ds.createDefaultDragGestureRecognizer(comp,DnDConstants.ACTION_COPY_OR_MOVE,this);
     }
     public void dragGestureRecognized(DragGestureEvent dge) {
       Object obj = null;
       String s = null;
       try {
         obj = dm.invoke(comp,new Object[]{});
         if (obj instanceof Table) {   // Drag data is Table
           s = ((Table)obj).toString();
         } else {                      // Drag data is XML
           s=(String)obj;
         }
         ds.startDrag(dge,DragSource.DefaultCopyDrop,new StringSelection(s),this);
       } catch (Exception e) {
         e.printStackTrace();
       }
     }
     public void dragDropEnd(DragSourceDropEvent e) {}
     public void dragEnter(DragSourceDragEvent e) {
       e.getDragSourceContext().setCursor(DragSource.DefaultCopyDrop); // entered valid drop area
     }
     public void dragExit(DragSourceEvent e) {
       e.getDragSourceContext().setCursor(DragSource.DefaultCopyNoDrop); // needed for LIST to have cursor update when over invalid drop region
     }
     public void dragOver(DragSourceDragEvent e) {}
     public void dropActionChanged(DragSourceDragEvent e) {}
  }

  public void stateChanged(ChangeEvent e) {
    if (!needsFullDraw) needsFullDraw = true;  // denotes that the tab just came into view so we need a full draw
  }

  /** Obtain a GridSheet object reference.
   *  This interface is used to de-couple nxm.sys.libg from nxm.sys.prim package.
   *  @since NeXtMidas 3.5.4
   */
  @ProvisionalUseOnly("Added in NeXtMidas 3.5.4")
  public static interface GridSheetReference {
    /** Get the GridSheet for this object
     *  @return the GridSheet for this object, null if none is available
     */
    GridSheet getGridSheet();
  }
}
