edu.ku.brc.ui.tmanfe.SpreadSheet.java Source code

Java tutorial

Introduction

Here is the source code for edu.ku.brc.ui.tmanfe.SpreadSheet.java

Source

/* Copyright (C) 2015, University of Kansas Center for Research
 * 
 * Specify Software Project, specify@ku.edu, Biodiversity Institute,
 * 1345 Jayhawk Boulevard, Lawrence, Kansas, 66045, USA
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
package edu.ku.brc.ui.tmanfe;

import static edu.ku.brc.ui.UIRegistry.getResourceString;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Comparator;
import java.util.EventObject;
import java.util.Hashtable;
import java.util.StringTokenizer;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SortOrder;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.text.JTextComponent;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
/*import org.jdesktop.swingx.decorator.Filter;
import org.jdesktop.swingx.decorator.FilterPipeline;
import org.jdesktop.swingx.decorator.SortController;
import org.jdesktop.swingx.decorator.Sorter;
*/

import edu.ku.brc.af.core.UsageTracker;
import edu.ku.brc.ui.IconManager;
import edu.ku.brc.ui.SearchableJXTable;
import edu.ku.brc.ui.UIHelper;
import edu.ku.brc.ui.UIRegistry;
import edu.ku.brc.ui.UIHelper.OSTYPE;
import edu.ku.brc.util.Pair;

/***************************************************************************************************
 * 
 * This class implements a basic spreadsheet using a JTable. It also provides a main() method to be
 * run as an application.
 * 
 * @version 1.0 July-2002
 * @author Thierry Manf, Rod Spears
 * 
 **************************************************************************************************/
@SuppressWarnings("serial")
public class SpreadSheet extends SearchableJXTable implements ActionListener {
    protected static final Logger log = Logger.getLogger(SpreadSheet.class);

    /**
     * Set this field to true and recompile to get debug traces
     */
    public static final boolean DEBUG = true;

    protected SpreadSheetModel model;
    protected JScrollPane scrollPane;
    protected JPopupMenu popupMenu;
    protected Action deleteAction = null;

    protected boolean useRowScrolling = false;

    // Members needed for the RowHeader    
    protected int rowLabelWidth = 0; // the width of the each row's label
    protected JPanel rowHeaderPanel;
    protected RHCellMouseAdapter rhCellMouseAdapter;
    protected Border cellBorder = null;
    protected Font cellFont;

    // Cell Selection
    protected boolean mouseDown = false;
    private boolean rowSelectionStarted = false;
    private Pair<Integer, Integer> emphasizedCell = null;
    protected SearchReplacePanel findPanel = null;

    protected CellRenderer customCellRenderer = new CellRenderer();

    protected int[] pastedRows = { -1, -1 }; //initial row and number of rows pasted

    // XXX Fix for Mac OS X Java 5 Bug
    protected int prevRowSelInx = -1;
    protected int prevColSelInx = -1;

    //no editing allowed when isReadOnly is true
    protected boolean isReadOnly = false;

    /**
     * Constructor for Spreadsheet from model
     * @param model
     */
    public SpreadSheet(final SpreadSheetModel model) {
        super(model);

        this.model = model;
        buildSpreadsheet();
    }

    /**
     * @return the Find Replace Panel.
     */
    public SearchReplacePanel getFindReplacePanel() {
        log.debug("Getting mySearchPanel");
        if (findPanel == null) {
            findPanel = createSearchReplacePanel();
        }
        return findPanel;
    }

    protected SearchReplacePanel createSearchReplacePanel() {
        return new SearchReplacePanel(this);
    }

    /* (non-Javadoc)
    * @see javax.swing.JTable#changeSelection(int, int, boolean, boolean)
    */
    @Override
    public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
        super.changeSelection(rowIndex, columnIndex, toggle, extend);
        clearEmphasis();
    }

    /* (non-Javadoc)
     * @see javax.swing.JTable#clearSelection()
     */
    @Override
    public void clearSelection() {
        super.clearSelection();
        clearEmphasis();
    }

    /**
     * Sets emphasized cell to null and updates ui.
     */
    protected void clearEmphasis() {
        Pair<Integer, Integer> ec = emphasizedCell;
        setEmphasizedCell(null);
        if (ec != null) {
            getModel().fireTableCellUpdated(ec.getFirst(), ec.getSecond());
        }

    }

    /**
      * 
      */
    protected void buildSpreadsheet() {

        this.setShowGrid(true);

        int numRows = model.getRowCount();

        scrollPane = new JScrollPane(this, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

        final SpreadSheet ss = this;
        JButton cornerBtn = UIHelper.createIconBtn("Blank", IconManager.IconSize.Std16, "SelectAll",
                new ActionListener() {
                    public void actionPerformed(ActionEvent ae) {
                        ss.selectAll();
                    }
                });
        cornerBtn.setEnabled(true);
        scrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, cornerBtn);

        // Allows row and collumn selections to exit at the same time
        setCellSelectionEnabled(true);

        setRowSelectionAllowed(true);
        setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);

        addMouseListener(new MouseAdapter() {
            /* (non-Javadoc)
             * @see java.awt.event.MouseAdapter#mousePressed(java.awt.event.MouseEvent)
             */
            @SuppressWarnings("synthetic-access")
            @Override
            public void mouseReleased(MouseEvent e) {
                // XXX For Java 5 Bug
                prevRowSelInx = getSelectedRow();
                prevColSelInx = getSelectedColumn();

                if (e.getClickCount() == 2) {
                    int rowIndexStart = getSelectedRow();
                    int colIndexStart = getSelectedColumn();

                    ss.editCellAt(rowIndexStart, colIndexStart);
                    if (ss.getEditorComponent() != null && ss.getEditorComponent() instanceof JTextComponent) {
                        ss.getEditorComponent().requestFocus();

                        final JTextComponent txtComp = (JTextComponent) ss.getEditorComponent();
                        String txt = txtComp.getText();
                        FontMetrics fm = txtComp.getFontMetrics(txtComp.getFont());
                        int x = e.getPoint().x - ss.getEditorComponent().getBounds().x - 1;
                        int prevWidth = 0;
                        for (int i = 0; i < txt.length(); i++) {

                            int width = fm.stringWidth(txt.substring(0, i));
                            int basePlusHalf = prevWidth + (int) (((width - prevWidth) / 2) + 0.5);
                            //System.out.println(prevWidth + " X[" + x + "] " + width+" ["+txt.substring(0, i)+"] " + i + " " + basePlusHalf);
                            //System.out.println(" X[" + x + "] " + prevWidth + " - "+ basePlusHalf+" - " + width+" ["+txt.substring(0, i)+"] " + i);
                            if (x < width) {
                                // Clearing the selection is needed for Window for some reason
                                final int inx = i + (x <= basePlusHalf ? -1 : 0);
                                SwingUtilities.invokeLater(new Runnable() {
                                    @SuppressWarnings("synthetic-access")
                                    public void run() {
                                        txtComp.setSelectionStart(0);
                                        txtComp.setSelectionEnd(0);
                                        txtComp.setCaretPosition(inx > 0 ? inx : 0);
                                    }
                                });
                                break;
                            }
                            prevWidth = width;
                        }
                    }
                }
            }
        });

        // Create a row-header to display row numbers.
        // This row-header is made of labels whose Borders,
        // Foregrounds, Backgrounds, and Fonts must be
        // the one used for the table column headers.
        // Also ensure that the row-header labels and the table
        // rows have the same height.

        //i have no idea WHY this has to be called.  i rearranged
        //the table and find replace panel, 
        // i started getting an array index out of
        //bounds on the column header ON MAC ONLY.  
        //tried firing this off, first and it fixed the problem.//meg
        this.getModel().fireTableStructureChanged();

        /*
         * Create the Row Header Panel
         */
        rowHeaderPanel = new JPanel((LayoutManager) null);

        if (getColumnModel().getColumnCount() > 0) {
            TableColumn column = getColumnModel().getColumn(0);
            TableCellRenderer renderer = getTableHeader().getDefaultRenderer();
            if (renderer == null) {
                renderer = column.getHeaderRenderer();
            }

            Component cellRenderComp = renderer.getTableCellRendererComponent(this, column.getHeaderValue(), false,
                    false, -1, 0);
            cellFont = cellRenderComp.getFont();

        } else {
            cellFont = (new JLabel()).getFont();
        }

        // Calculate Row Height
        cellBorder = (Border) UIManager.getDefaults().get("TableHeader.cellBorder");
        Insets insets = cellBorder.getBorderInsets(tableHeader);
        FontMetrics metrics = getFontMetrics(cellFont);

        rowHeight = insets.bottom + metrics.getHeight() + insets.top;
        rowLabelWidth = metrics.stringWidth("9999") + insets.right + insets.left;

        Dimension dim = new Dimension(rowLabelWidth, rowHeight * numRows);
        rowHeaderPanel.setPreferredSize(dim); // need to call this when no layout manager is used.

        rhCellMouseAdapter = new RHCellMouseAdapter(this);

        // Adding the row header labels
        for (int ii = 0; ii < numRows; ii++) {
            addRow(ii, ii + 1, false);
        }

        JViewport viewPort = new JViewport();
        dim.height = rowHeight * numRows;
        viewPort.setViewSize(dim);
        viewPort.setView(rowHeaderPanel);
        scrollPane.setRowHeader(viewPort);

        // Experimental from the web, but I think it does the trick.
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (!ss.isEditing() && !e.isActionKey() && !e.isControlDown() && !e.isMetaDown() && !e.isAltDown()
                        && e.getKeyCode() != KeyEvent.VK_SHIFT && e.getKeyCode() != KeyEvent.VK_TAB
                        && e.getKeyCode() != KeyEvent.VK_ENTER) {
                    log.error("Grabbed the event as input");

                    int rowIndexStart = getSelectedRow();
                    int colIndexStart = getSelectedColumn();

                    if (rowIndexStart == -1 || colIndexStart == -1)
                        return;

                    ss.editCellAt(rowIndexStart, colIndexStart);
                    Component c = ss.getEditorComponent();
                    if (c instanceof JTextComponent)
                        ((JTextComponent) c).setText("");
                }
            }
        });

        resizeAndRepaint();

        // Taken from a JavaWorld Example (But it works)
        KeyStroke cut = KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(),
                false);
        KeyStroke copy = KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(),
                false);
        KeyStroke paste = KeyStroke.getKeyStroke(KeyEvent.VK_V,
                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);

        Action ssAction = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                SpreadSheet.this.actionPerformed(e);
            }
        };

        getInputMap().put(cut, "Cut");
        getActionMap().put("Cut", ssAction);

        getInputMap().put(copy, "Copy");
        getActionMap().put("Copy", ssAction);

        getInputMap().put(paste, "Paste");
        getActionMap().put("Paste", ssAction);

        ((JMenuItem) UIRegistry.get(UIRegistry.COPY)).addActionListener(this);
        ((JMenuItem) UIRegistry.get(UIRegistry.CUT)).addActionListener(this);
        ((JMenuItem) UIRegistry.get(UIRegistry.PASTE)).addActionListener(this);

        setSortOrderCycle(SortOrder.ASCENDING, SortOrder.DESCENDING, SortOrder.UNSORTED);
    }

    /**
      * Appends a new Row onto the spreadsheet.
      * @param rowInx the last index
      * @param adjustPanelSize whether to resize the header panel
      */
    protected void addRow(final int rowInx, final int rowNum, final boolean adjustPanelSize) {
        RowHeaderLabel lbl = new RowHeaderLabel(rowNum, cellFont);
        lbl.setBounds(0, rowInx * rowHeight, rowLabelWidth, rowHeight);
        //System.out.println(rowNum+"  "+lbl.getBounds());
        if (UIHelper.getOSType() != UIHelper.OSTYPE.MacOSX) {
            lbl.setBorder(cellBorder);
        }
        lbl.addMouseListener(rhCellMouseAdapter);
        //lbl.addMouseMotionListener(rhCellMouseAdapter);
        rowHeaderPanel.add(lbl);

        if (adjustPanelSize) {
            Dimension dim = new Dimension(rowLabelWidth, rowHeight * (rowInx + 1));
            rowHeaderPanel.setPreferredSize(dim);
            rowHeaderPanel.setSize(dim);
            resizeAndRepaint();
        }
    }

    /**
     * Appends a new row onto the Spreadsheet. 
     */
    public void addRow() {
        addRow(getModel().getRowCount() - 1, getModel().getRowCount(), true);
    }

    /**
     * @param deleteAction the deleteAction to set
     */
    public void setDeleteAction(Action deleteAction) {
        this.deleteAction = deleteAction;
    }

    /**
     * Must be called AFTER the model has been adjusted.
     * @param rowInx the row index that was removed
     */
    public void removeRow(final int rowInx, final boolean doSelection) {
        int rowCount = getModel().getRowCount();

        Component comp = rowHeaderPanel.getComponent(rowCount);
        rowHeaderPanel.remove(comp);

        Dimension dim = new Dimension(rowLabelWidth, rowHeight * (rowCount));
        rowHeaderPanel.setPreferredSize(dim);
        rowHeaderPanel.setSize(dim);
        rowHeaderPanel.validate();

        resizeAndRepaint();

        if (doSelection && rowCount > 0) {
            if (rowInx >= rowCount) {
                setRowSelectionInterval(rowCount - 1, rowCount - 1);
            } else {
                setRowSelectionInterval(rowInx, rowInx);
            }
            setColumnSelectionInterval(0, getColumnCount() - 1);
        }
    }

    /**
     * Scrolls to a specified row.
     * @param row the row to scroll to (zero-based)
     */
    public void scrollToRow(final int row) {
        Rectangle r = getCellRect(row, 0, true);
        scrollRectToVisible(r);
    }

    /**
     * @return an array of indexes INTO THE MODEL of the currently selected rows
     */
    public int[] getSelectedRowModelIndexes() {
        int[] selected = getSelectedRows();
        for (int j = 0; j < selected.length; ++j) {
            int modelIndex = convertRowIndexToModel(selected[j]);
            selected[j] = modelIndex;
        }
        return selected;
    }

    /**
     * @return an array of indexes INTO THE MODEL of the currently selected columns
     */
    public int[] getSelectedColumnModelIndexes() {
        int[] selected = getSelectedColumns();
        for (int j = 0; j < selected.length; ++j) {
            int modelIndex = convertColumnIndexToModel(selected[j]);
            selected[j] = modelIndex;
        }
        return selected;
    }

    /**
     * Invoked when a cell edition starts. This method overrides and calls that of its super class.
     * 
     * @param int The row to be edited
     * @param int The column to be edited
     * @param EventObject The firing event
     * @return boolean false if for any reason the cell cannot be edited.
     */
    @Override
    public boolean editCellAt(int row, int column, EventObject ev) {
        return mouseDown ? false : isReadOnly ? false : super.editCellAt(row, column, ev);
    }

    /**
     * Invoked by the cell editor when a cell edition stops. This method override and calls that of
     * its super class.
     * 
     */
    @Override
    public void editingStopped(ChangeEvent ev) {
        //_model.setDisplayMode(_editedModelRow, _editedModelCol);
        super.editingStopped(ev);
    }

    /**
     * Invoked by the cell editor when a cell edition is cancelled. This method override and calls
     * that of its super class.
     * 
     */
    @Override
    public void editingCanceled(ChangeEvent ev) {
        //_model.setDisplayMode(_editedModelRow, _editedModelCol);
        super.editingCanceled(ev);
    }

    /**
     * @return the scroll pane.
     */
    public JScrollPane getScrollPane() {
        return scrollPane;
    }

    /**
     * CReates the popup menu for a cell. (THis really needs to be moved outside of this class).
     * @param pnt the point to pop it up
     * @return the popup menu
     */
    protected JPopupMenu createMenuForSelection(final Point pnt) {
        //final int row = rowAtPoint(pnt);

        Class<?> cellClass = getModel().getColumnClass(convertColumnIndexToModel(columnAtPoint(pnt)));
        boolean isImage = cellClass == ImageIcon.class || cellClass == Image.class;

        JPopupMenu pMenu = new JPopupMenu();
        UsageTracker.incrUsageCount("WB.SpreadsheetContextMenu");
        if (getSelectedColumnCount() == 1) {
            final int[] rows = getSelectedRowModelIndexes();
            if (rows.length > 1) {
                //if (row == rows[0])
                //{
                if (!isImage) {
                    JMenuItem mi = pMenu.add(new JMenuItem(UIRegistry.getResourceString("SpreadSheet.FillDown")));
                    mi.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent ae) {
                            int selectedUICol = getSelectedColumn();
                            int selectedModelCol = convertColumnIndexToModel(selectedUICol);
                            model.fill(selectedModelCol, rows[0], rows);
                            popupMenu.setVisible(false);
                        }
                    });
                }
                //} else if (row == rows[rows.length-1])
                //{
                if (!isImage) {
                    JMenuItem mi = pMenu.add(new JMenuItem(UIRegistry.getResourceString("Spreadsheet.FillUp")));
                    mi.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent ae) {
                            int selectedUICol = getSelectedColumn();
                            int selectedModelCol = convertColumnIndexToModel(selectedUICol);
                            model.fill(selectedModelCol, rows[rows.length - 1], rows);
                            popupMenu.setVisible(false);
                        }
                    });
                }
                //}
            }
        }

        if (!isImage) {
            JMenuItem mi = pMenu.add(new JMenuItem(UIRegistry.getResourceString("SpreadSheet.ClearCells")));
            mi.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    int[] rows = getSelectedRowModelIndexes();
                    int[] cols = getSelectedColumnModelIndexes();

                    model.clearCells(rows, cols);
                    popupMenu.setVisible(false);
                }
            });
        }

        if (deleteAction != null) {
            JMenuItem mi = pMenu.add(new JMenuItem(UIRegistry.getResourceString("SpreadSheet.DeleteRows")));
            mi.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    deleteAction.actionPerformed(ae);
                    popupMenu.setVisible(false);
                }
            });
        }

        //add copy, paste, cut
        if (!isImage) //copy, paste currently only implemented for string data
        {
            boolean isSelection = getSelectedColumnCount() > 0 && getSelectedRowCount() > 0;
            if (pMenu.getComponentCount() > 0) {
                pMenu.add(new JPopupMenu.Separator());
            }
            JMenuItem mi = pMenu.add(new JMenuItem(UIRegistry.getResourceString("CutMenu")));
            mi.setEnabled(isSelection && !isImage); // copy, paste currently
            // only implemented for
            // string data
            mi.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    SwingUtilities.invokeLater(new Runnable() {

                        /*
                         * (non-Javadoc)
                         * 
                         * @see java.lang.Runnable#run()
                         */
                        @Override
                        public void run() {
                            cutOrCopy(true);

                        }

                    });
                }
            });
            mi = pMenu.add(new JMenuItem(UIRegistry.getResourceString("CopyMenu")));
            mi.setEnabled(isSelection && !isImage);
            mi.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    SwingUtilities.invokeLater(new Runnable() {

                        /*
                         * (non-Javadoc)
                         * 
                         * @see java.lang.Runnable#run()
                         */
                        @Override
                        public void run() {
                            cutOrCopy(false);

                        }

                    });
                }
            });
            mi = pMenu.add(new JMenuItem(UIRegistry.getResourceString("PasteMenu")));
            mi.setEnabled(isSelection && !isImage && canPasteFromClipboard());
            mi.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    SwingUtilities.invokeLater(new Runnable() {

                        /*
                         * (non-Javadoc)
                         * 
                         * @see java.lang.Runnable#run()
                         */
                        @Override
                        public void run() {
                            paste();

                        }

                    });
                }
            });

        }

        pMenu.setInvoker(this);
        return pMenu;
    }

    /* (non-Javadoc)
     * @see javax.swing.JComponent#processMouseEvent(java.awt.event.MouseEvent)
     */
    @Override
    public void processMouseEvent(MouseEvent ev) {
        int type = ev.getID();
        //int modifiers = ev.getModifiers();

        mouseDown = type == MouseEvent.MOUSE_PRESSED;

        // XXX For Java 5 Bug
        // I am not sure if we still need this
        if (mouseDown && UIHelper.getOSType() == UIHelper.OSTYPE.MacOSX) {
            int rowIndexStart = rowAtPoint(ev.getPoint());
            int colIndexStart = columnAtPoint(ev.getPoint());

            //System.out.println(isEditing()+"  "+rowIndexStart+" "+colIndexStart+" "+prevRowSelInx+" "+prevColSelInx+" ");
            if (isEditing() && (prevRowSelInx != rowIndexStart || prevColSelInx != colIndexStart)) {
                getCellEditor().stopCellEditing();
            }
        }
        // Done - For Java 5 Bug

        if (ev.isPopupTrigger()) {
            // No matter what, stop editing if we are editing
            if (isEditing()) {
                getCellEditor().stopCellEditing();
            }

            // Now check to see if we right clicked on a different rowor column
            boolean isOnSelectedCell = false;
            int rowIndexStart = rowAtPoint(ev.getPoint());
            int colIndexStart = columnAtPoint(ev.getPoint());

            // Check to see if we are in the selection of mulitple rows and cols
            if (getSelectedRowCount() > 0) {
                int[] cols = getSelectedColumns();
                for (int r : getSelectedRows()) {
                    if (r == rowIndexStart) {
                        for (int c : cols) {
                            if (c == colIndexStart) {
                                isOnSelectedCell = true;
                                break;
                            }
                        }
                    }
                }
            }
            if (!isOnSelectedCell) {
                setRowSelectionInterval(rowIndexStart, rowIndexStart);
                setColumnSelectionInterval(colIndexStart, colIndexStart);
            }

            if (popupMenu != null) {
                popupMenu.setVisible(false);
            }

            popupMenu = createMenuForSelection(ev.getPoint());

            if (popupMenu.isVisible()) {
                popupMenu.setVisible(false);

            } else {
                //popupMenu.setTargetCells(_selection);
                Point p = getLocationOnScreen();
                popupMenu.setLocation(p.x + ev.getX() + 1, p.y + ev.getY() + 1);
                popupMenu.setVisible(true);
            }
        }
        super.processMouseEvent(ev);
    }

    /* (non-Javadoc)
     * @see javax.swing.JTable#processKeyBinding(javax.swing.KeyStroke, java.awt.event.KeyEvent, int, boolean)
     */
    @Override
    protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
        if (e.getKeyCode() == KeyEvent.VK_META) {
            return false;
        }
        return super.processKeyBinding(ks, e, condition, pressed);
    }

    protected boolean canCutOrCopy() {
        int numcols = getSelectedColumnCount();
        int numrows = getSelectedRowCount();
        int[] rowsselected = getSelectedRows();
        int[] colsselected = getSelectedColumns();
        if (numcols == 0 || numrows == 0
                || !((numrows - 1 == rowsselected[rowsselected.length - 1] - rowsselected[0]
                        && numrows == rowsselected.length)
                        && (numcols - 1 == colsselected[colsselected.length - 1] - colsselected[0]
                                && numcols == colsselected.length))) {
            //JOptionPane.showMessageDialog(null, "Invalid Copy Selection",
            //        "Invalid Copy Selection", JOptionPane.ERROR_MESSAGE);
            return false;
        }
        return true;
    }

    /**
     * @param isCut
     * 
     * Cut or copy selected cells into the Clipboard
     */
    public void cutOrCopy(final boolean isCut) {
        if (!canCutOrCopy()) {
            return;
        }
        StringBuffer sbf = new StringBuffer();
        // Check to ensure we have selected only a contiguous block of
        // cells
        int numcols = getSelectedColumnCount();
        int numrows = getSelectedRowCount();
        int[] rowsselected = getSelectedRows();
        int[] colsselected = getSelectedColumns();

        for (int i = 0; i < numrows; i++) {
            for (int j = 0; j < numcols; j++) {
                Object val = getValueAt(rowsselected[i], colsselected[j]);
                if (val == null || "".equals(val)) //Add place holder for empty cell
                {
                    val = "\b";
                }
                sbf.append(val);
                if (j < numcols - 1) {
                    sbf.append("\t");
                }
                if (isCut) {
                    setValueAt("", rowsselected[i], colsselected[j]);
                }
            }
            if (numrows > 1) {
                sbf.append("\n");
            }
        }
        UIHelper.setTextToClipboard(sbf.toString());
    }

    /**
     * @return true if pastable data is present in the clipboard
     */
    protected boolean canPasteFromClipboard() {
        Clipboard sysClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        return sysClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor);
    }

    /**
     * Paste data from clipboard into spreadsheet.
     * Currently only implemented for string data.
     */
    public void paste() {
        //System.out.println("Trying to Paste");
        int[] rows = getSelectedRows();
        int[] cols = getSelectedColumns();
        pastedRows[0] = -1;
        pastedRows[1] = -1;
        if (rows != null && cols != null && rows.length > 0 && cols.length > 0) {
            int startRow = rows[0];
            pastedRows[0] = startRow;
            int startCol = cols[0];
            try {
                Clipboard sysClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                if (sysClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
                    String trstring = (String) sysClipboard.getData(DataFlavor.stringFlavor);
                    StringTokenizer st1 = new StringTokenizer(trstring, "\n\r");
                    for (int i = 0; st1.hasMoreTokens(); i++) {
                        String rowstring = st1.nextToken();
                        //System.out.println("Row [" + rowstring+"]");
                        String[] tokens = StringUtils.splitPreserveAllTokens(rowstring, '\t');
                        for (int j = 0; j < tokens.length; j++) {
                            if (startRow + i < getRowCount() && startCol + j < getColumnCount()) {
                                int colInx = startCol + j;
                                if (tokens[j].length() <= model.getColDataLen(colInx)) {
                                    String token = tokens[j];
                                    if ("\b".equals(token)) //is placeholder for empty cell
                                    {
                                        token = "";
                                    }
                                    setValueAt(token, startRow + i, colInx);
                                } else {
                                    String msg = String.format(getResourceString("UI_NEWDATA_TOO_LONG"),
                                            new Object[] { model.getColumnName(startCol + j),
                                                    model.getColDataLen(colInx) });
                                    UIRegistry.getStatusBar().setErrorMessage(msg);
                                    Toolkit.getDefaultToolkit().beep();
                                }
                            }
                            //System.out.println("Putting [" + tokens[j] + "] at row=" + startRow + i + "column=" + startCol + j);
                        }
                        pastedRows[1] = pastedRows[1] + 1;
                    }
                }
            } catch (IllegalStateException ex) {
                UIRegistry.displayStatusBarErrMsg(getResourceString("Spreadsheet.ClipboardUnavailable"));
            } catch (Exception ex) {
                edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(SpreadSheet.class, ex);
                ex.printStackTrace();
            }
        }
    }

    /* (non-Javadoc)
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(final ActionEvent e) {
        if (UIRegistry.getPermanentFocusOwner() != this) // bail
        {
            return;
        }

        //
        // The code in this method was tken from a JavaWorld Example
        //
        final boolean isCut = e.getActionCommand().compareTo("Cut") == 0 || e.getActionCommand().equals("x");
        if (e.getActionCommand().compareTo("Copy") == 0 || e.getActionCommand().equals("c") || isCut) {
            SwingUtilities.invokeLater(new Runnable() {

                /* (non-Javadoc)
                 * @see java.lang.Runnable#run()
                 */
                @Override
                public void run() {
                    cutOrCopy(isCut);
                }

            });
        } else if (e.getActionCommand().compareTo("Paste") == 0 || e.getActionCommand().equals("v")) {
            SwingUtilities.invokeLater(new Runnable() {

                /* (non-Javadoc)
                 * @see java.lang.Runnable#run()
                 */
                @Override
                public void run() {
                    paste();
                }

            });
        }
    }

    /* (non-Javadoc)
     * @see javax.swing.JComponent#setVisible(boolean)
     */
    @Override
    public void setVisible(boolean flag) {
        scrollPane.setVisible(flag);
    }

    /**
     * @return
     */
    public int getMaxUnitIncrement() {
        if (getModel() == null) {
            return 0;
        }
        int cols = getModel().getColumnCount();
        if (cols > 0) {
            cols--;
        }
        double unit = getPreferredSize().width / cols;
        return (int) unit;
    }

    /* (non-Javadoc)
     * @see javax.swing.JTable#getScrollableBlockIncrement(java.awt.Rectangle, int, int)
     */
    @Override
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
        if (useRowScrolling) {
            if (orientation == SwingConstants.HORIZONTAL) {
                return visibleRect.width - getMaxUnitIncrement();
            }
            return visibleRect.height - getMaxUnitIncrement();
        }

        return super.getScrollableBlockIncrement(visibleRect, orientation, direction);

    }

    /* (non-Javadoc)
     * @see javax.swing.JTable#getScrollableTracksViewportHeight()
     */
    @Override
    public boolean getScrollableTracksViewportHeight() {
        return useRowScrolling ? false : super.getScrollableTracksViewportHeight();
    }

    /* (non-Javadoc)
     * @see javax.swing.JTable#getScrollableTracksViewportWidth()
     */
    @Override
    public boolean getScrollableTracksViewportWidth() {
        return useRowScrolling ? false : super.getScrollableTracksViewportWidth();
    }

    /* (non-Javadoc)
     * @see javax.swing.JTable#getScrollableUnitIncrement(java.awt.Rectangle, int, int)
     */
    @Override
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        if (useRowScrolling) {
            // Get the current position.
            int currentPosition = 0;
            if (orientation == SwingConstants.HORIZONTAL) {
                currentPosition = visibleRect.x;
            } else {
                currentPosition = visibleRect.y;
            }

            // Return the number of pixels between currentPosition
            // and the nearest tick mark in the indicated direction.
            if (direction < 0) {
                int newPosition = currentPosition
                        - (currentPosition / getMaxUnitIncrement()) * getMaxUnitIncrement();
                return (newPosition == 0) ? getMaxUnitIncrement() : newPosition;
            }
            // else
            return ((currentPosition / getMaxUnitIncrement()) + 1) * getMaxUnitIncrement() - currentPosition;
        }
        return super.getScrollableUnitIncrement(visibleRect, orientation, direction);
    }

    /**
     * @param row
     * @param column
     * 
     * Set emphasis at row and column
     */
    public void setEmphasizedCell(int row, int column) {
        if (emphasizedCell == null) {
            emphasizedCell = new Pair<Integer, Integer>(row, column);
        } else {
            emphasizedCell.setFirst(row);
            emphasizedCell.setSecond(column);
        }
    }

    /**
     * 
     * @param cell
     * 
     * Set the emphasized cell
     */
    public void setEmphasizedCell(final Pair<Integer, Integer> cell) {
        emphasizedCell = cell;
    }

    public boolean isEmphasizedCell(int row, int column) {
        return emphasizedCell == null ? false
                : emphasizedCell.getFirst().equals(row) && emphasizedCell.getSecond().equals(column) ? true : false;
    }

    /* (non-Javadoc)
    * @see org.jdesktop.swingx.JXTable#getCellRenderer(int, int)
    */
    @Override
    public TableCellRenderer getCellRenderer(int row, int column) {
        if (isEmphasizedCell(row, column)) {
            return customCellRenderer;
        }
        return super.getCellRenderer(row, column);
    }

    /**
     * @return the isReadOnly
     */
    public boolean isReadOnly() {
        return isReadOnly;
    }

    /**
     * @param isReadOnly the isReadOnly to set
     */
    public void setReadOnly(boolean isReadOnly) {
        this.isReadOnly = isReadOnly;
    }

    /**
     * @author timbo
     * 
     * Makes a three-state toggle for column sort state - ascending, descending, and unsorted
     *
     *Copied directly from example at API documentation for org.jdesktop.swingx.JXTable
     */
    //    public class CustomToggleSortOrderFP extends FilterPipeline
    //   {
    //
    //      /**
    //       * 
    //       */
    //      public CustomToggleSortOrderFP()
    //      {
    //         super();
    //      }
    //
    //      /**
    //       * @param inList
    //       */
    //      public CustomToggleSortOrderFP(Filter[] inList)
    //      {
    //         super(inList);
    //      }
    //
    //      /* (non-Javadoc)
    //       * @see org.jdesktop.swingx.decorator.FilterPipeline#createDefaultSortController()
    //       */
    //      @Override
    //      protected SortController createDefaultSortController()
    //      {
    //         return new CustomSortController();
    //      }
    //
    //      /**
    //       * @author timbo
    //       *
    //       */
    //      protected class CustomSortController extends SorterBasedSortController
    //      {
    //
    //         /* (non-Javadoc)
    //          * @see org.jdesktop.swingx.decorator.FilterPipeline.SorterBasedSortController#toggleSortOrder(int, java.util.Comparator)
    //          */
    //         @Override
    //         @SuppressWarnings("unchecked")
    //         public void toggleSortOrder(int column, Comparator comparator)
    //         {
    //            Sorter currentSorter = getSorter();
    //            if ((currentSorter != null)
    //                  && (currentSorter.getColumnIndex() == column)
    //                  && !currentSorter.isAscending())
    //            {
    //               setSorter(null);
    //            } else
    //            {
    //               super.toggleSortOrder(column, comparator);
    //            }
    //         }
    //
    //      }
    //   }

    /*
      * This class is used to customize the cells rendering.
      */
    public class CellRenderer extends JLabel implements TableCellRenderer {

        private Border _selectBorder;
        private EmptyBorder _emptyBorder;
        private Border _emphasizedBorder;
        //private Dimension   _dim;

        public CellRenderer() {
            super();
            _emptyBorder = new EmptyBorder(2, 2, 2, 2);
            _selectBorder = new LineBorder(Color.BLUE);
            _emphasizedBorder = new LineBorder(Color.BLUE);
            //_selectBorder = BorderFactory.createBevelBorder(BevelBorder.LOWERED);
            //setOpaque(true);
            //setHorizontalAlignment(SwingConstants.CENTER);
            //_dim = new Dimension();
            //_dim.height = 22;
            //_dim.width = 100;
            //setSize(_dim);
        }

        /**
         *
         * Method defining the renderer to be used 
         * when drawing the cells.
         *
         */
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                boolean hasFocus, int row, int column) {
            boolean isEmphasized = emphasizedCell == null ? false
                    : emphasizedCell.getFirst().equals(row) && emphasizedCell.getSecond().equals(column) ? true
                            : false;
            Border border = null;
            if (isEmphasized) {
                border = _emphasizedBorder;
                log.info("Emphasized Cell: " + emphasizedCell.getFirst() + ", " + emphasizedCell.getSecond());
            } else {
                border = isSelected ? _selectBorder : _emptyBorder;
            }
            setBorder(border);
            setText(value.toString());

            return this;

        }

    }

    class RowHeaderLabel extends JComponent {
        protected String rowNumStr;
        protected int rowNum;
        protected Font font;

        protected int labelWidth = Integer.MAX_VALUE;
        protected int labelheight = Integer.MAX_VALUE;

        public RowHeaderLabel(int rowNum, final Font font) {
            this.rowNum = rowNum;
            this.rowNumStr = Integer.toString(rowNum);
            this.font = font;
        }

        public int getRowNum() {
            return rowNum;
        }

        /* (non-Javadoc)
         * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
         */
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            g.setFont(font);

            if (labelWidth == Integer.MAX_VALUE) {
                FontMetrics fm = getFontMetrics(font);
                labelheight = fm.getAscent();
                labelWidth = fm.stringWidth(rowNumStr);
            }

            Insets ins = getInsets();
            Dimension size = this.getSize();
            int y = size.height - ((size.height - labelheight) / 2) - ins.bottom;

            g.drawString(rowNumStr, (size.width - labelWidth) / 2, y);
        }
    }

    /* (non-Javadoc)
     * @see javax.swing.JTable#getModel()
     */
    @Override
    public SpreadSheetModel getModel() {
        return (SpreadSheetModel) super.getModel();
    }

    /**
     * Cleans up references.
     */
    public void cleanUp() {
        UIHelper.removeMouseListeners(this);

        ((JMenuItem) UIRegistry.get(UIRegistry.COPY)).removeActionListener(this);
        ((JMenuItem) UIRegistry.get(UIRegistry.CUT)).removeActionListener(this);
        ((JMenuItem) UIRegistry.get(UIRegistry.PASTE)).removeActionListener(this);

        if (findPanel != null) {
            findPanel.cleanUp();
            findPanel = null;
        }

        if (scrollPane != null) {
            scrollPane.removeAll();
            scrollPane.setRowHeader(null);
        }

        if (rowHeaderPanel != null) {
            rowHeaderPanel.removeAll();
            rowHeaderPanel = null;
        }
        if (model != null) {
            model.cleanUp();
            /* Nulling out model (somehow) causes Null Pointer Exceptions 
             * after a WorkBench has been closed.
             * (see bugzilla number 5204
            model      = null;
            */
        }
        scrollPane = null;
        if (popupMenu != null) {
            popupMenu.setVisible(false);
            popupMenu = null;
        }

        if (rhCellMouseAdapter != null) {
            rhCellMouseAdapter.cleanUp();
            rhCellMouseAdapter = null;
        }
    }

    /**
     * MouseAdapter for selecting rows by clicking and dragging on the Row Headers.
     */
    class RHCellMouseAdapter extends MouseAdapter {
        protected JTable table;
        protected Hashtable<Integer, Boolean> selectionHash = new Hashtable<Integer, Boolean>();
        protected Hashtable<Integer, Boolean> doubleSelected = new Hashtable<Integer, Boolean>();
        protected int selAnchor = -1;
        protected int selLead = -1;

        // these fields are important when a user ctrl-clicks a row and then drags
        protected boolean ctrlWasDown = false;
        protected boolean dragIsDeselecting = false;

        public RHCellMouseAdapter(final JTable table) {
            this.table = table;
        }

        /* (non-Javadoc)
         * @see java.awt.event.MouseAdapter#mousePressed(java.awt.event.MouseEvent)
         */
        @SuppressWarnings("synthetic-access")
        @Override
        public void mousePressed(MouseEvent e) {
            log.debug("mousePressed entered");
            log.debug("anchor: " + selAnchor);
            log.debug("lead   :" + selLead);

            if (isEditing()) {
                getCellEditor().stopCellEditing();
            }

            RowHeaderLabel lbl = (RowHeaderLabel) e.getSource();
            int row = lbl.getRowNum() - 1;

            // toggle the selection state of the clicked row
            // and set the current row as the new anchor
            boolean ctrlDown = false;
            if (UIHelper.getOSType() == OSTYPE.MacOSX) {
                ctrlDown = e.isMetaDown();
            } else {
                ctrlDown = e.isControlDown();
            }
            if (ctrlDown) {
                ListSelectionModel selModel = table.getSelectionModel();

                // figure out the selection state of this row
                boolean wasSelected = table.getSelectionModel().isSelectedIndex(row);

                // toggle the selection state of this row
                if (wasSelected) {
                    // deselect it
                    selModel.removeSelectionInterval(row, row);
                    dragIsDeselecting = true;
                } else {
                    // select it and make it the new anchor
                    selModel.addSelectionInterval(row, row);
                    dragIsDeselecting = false;
                }
                selAnchor = row;
                selLead = row;
                ctrlWasDown = true;
            } else if (e.isShiftDown()) {
                ListSelectionModel selModel = table.getSelectionModel();

                selModel.removeSelectionInterval(selAnchor, selLead);
                selModel.addSelectionInterval(selAnchor, row);
                selLead = row;
            } else // no modifier keys are down
            {
                // just select the current row
                // and set it as the new anchor
                table.setRowSelectionInterval(row, row);

                table.setColumnSelectionInterval(0, table.getColumnCount() - 1);
                selAnchor = selLead = row;

                prevRowSelInx = getSelectedRow();
                prevColSelInx = 0;
            }

            rowSelectionStarted = true;
            table.getSelectionModel().setValueIsAdjusting(true);

            log.debug("anchor: " + selAnchor);
            log.debug("lead   :" + selLead);
            log.debug("mousePressed exited");
        }

        /* (non-Javadoc)
         * @see java.awt.event.MouseAdapter#mouseReleased(java.awt.event.MouseEvent)
         */
        @SuppressWarnings("synthetic-access")
        @Override
        public void mouseReleased(MouseEvent e) {
            log.debug("mouseReleased entered");
            log.debug("anchor: " + selAnchor);
            log.debug("lead   :" + selLead);

            // the user has released the mouse button, so we're done selecting rows
            //RowHeaderLabel lbl = (RowHeaderLabel)e.getSource();
            //int            row = lbl.getRowNum()-1;

            rowSelectionStarted = false;
            table.getSelectionModel().setValueIsAdjusting(false);
            ctrlWasDown = false;
            dragIsDeselecting = false;

            log.debug("anchor: " + selAnchor);
            log.debug("lead   :" + selLead);
            log.debug("mouseReleased exited");
        }

        /* (non-Javadoc)
         * @see java.awt.event.MouseAdapter#mouseEntered(java.awt.event.MouseEvent)
         */
        @SuppressWarnings("synthetic-access")
        @Override
        public void mouseEntered(MouseEvent e) {
            // the user has clicked and is dragging, we are (de)selecting multiple rows...
            if (rowSelectionStarted) {
                log.debug("mouseEntered entered");
                log.debug("anchor: " + selAnchor);
                log.debug("lead   :" + selLead);

                RowHeaderLabel lbl = (RowHeaderLabel) e.getSource();
                int row = lbl.getRowNum() - 1;
                selLead = row;
                if (ctrlWasDown) {
                    if (dragIsDeselecting) {
                        table.removeRowSelectionInterval(selAnchor, row);
                    } else {
                        table.addRowSelectionInterval(selAnchor, row);
                    }
                } else {
                    table.setRowSelectionInterval(selAnchor, row);
                }
                table.setColumnSelectionInterval(0, table.getColumnCount() - 1);
                log.debug("anchor: " + selAnchor);
                log.debug("lead   :" + selLead);
                log.debug("mouseEntered exited");
            }
        }

        /**
         * Cleans up references.
         */
        public void cleanUp() {
            this.table = null;
        }
    }

}