summaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java')
-rw-r--r--libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java1410
1 files changed, 1410 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java
new file mode 100644
index 000000000..f5a4bcb67
--- /dev/null
+++ b/libjava/classpath/javax/swing/plaf/basic/BasicTableUI.java
@@ -0,0 +1,1410 @@
+/* BasicTableUI.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.plaf.basic;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.ComponentOrientation;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ActionMap;
+import javax.swing.CellRendererPane;
+import javax.swing.DefaultCellEditor;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.InputMap;
+import javax.swing.JComponent;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.LookAndFeel;
+import javax.swing.SwingUtilities;
+import javax.swing.TransferHandler;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.MouseInputListener;
+import javax.swing.plaf.ActionMapUIResource;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.TableUI;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+import javax.swing.table.TableModel;
+
+public class BasicTableUI extends TableUI
+{
+ public static ComponentUI createUI(JComponent comp)
+ {
+ return new BasicTableUI();
+ }
+
+ protected FocusListener focusListener;
+ protected KeyListener keyListener;
+ protected MouseInputListener mouseInputListener;
+ protected CellRendererPane rendererPane;
+ protected JTable table;
+
+ /** The normal cell border. */
+ Border cellBorder;
+
+ /** The action bound to KeyStrokes. */
+ TableAction action;
+
+ /**
+ * Listens for changes to the tables properties.
+ */
+ private PropertyChangeListener propertyChangeListener;
+
+ /**
+ * Handles key events for the JTable. Key events should be handled through
+ * the InputMap/ActionMap mechanism since JDK1.3. This class is only there
+ * for backwards compatibility.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+ public class KeyHandler implements KeyListener
+ {
+
+ /**
+ * Receives notification that a key has been pressed and released.
+ * Activates the editing session for the focused cell by pressing the
+ * character keys.
+ *
+ * @param event the key event
+ */
+ public void keyTyped(KeyEvent event)
+ {
+ // Key events should be handled through the InputMap/ActionMap mechanism
+ // since JDK1.3. This class is only there for backwards compatibility.
+
+ // Editor activation is a specific kind of response to ''any''
+ // character key. Hence it is handled here.
+ if (!table.isEditing() && table.isEnabled())
+ {
+ int r = table.getSelectedRow();
+ int c = table.getSelectedColumn();
+ if (table.isCellEditable(r, c))
+ table.editCellAt(r, c);
+ }
+ }
+
+ /**
+ * Receives notification that a key has been pressed.
+ *
+ * @param event the key event
+ */
+ public void keyPressed(KeyEvent event)
+ {
+ // Key events should be handled through the InputMap/ActionMap mechanism
+ // since JDK1.3. This class is only there for backwards compatibility.
+ }
+
+ /**
+ * Receives notification that a key has been released.
+ *
+ * @param event the key event
+ */
+ public void keyReleased(KeyEvent event)
+ {
+ // Key events should be handled through the InputMap/ActionMap mechanism
+ // since JDK1.3. This class is only there for backwards compatibility.
+ }
+ }
+
+ public class FocusHandler implements FocusListener
+ {
+ public void focusGained(FocusEvent e)
+ {
+ // The only thing that is affected by a focus change seems to be
+ // how the lead cell is painted. So we repaint this cell.
+ repaintLeadCell();
+ }
+
+ public void focusLost(FocusEvent e)
+ {
+ // The only thing that is affected by a focus change seems to be
+ // how the lead cell is painted. So we repaint this cell.
+ repaintLeadCell();
+ }
+
+ /**
+ * Repaints the lead cell in response to a focus change, to refresh
+ * the display of the focus indicator.
+ */
+ private void repaintLeadCell()
+ {
+ int rowCount = table.getRowCount();
+ int columnCount = table.getColumnCount();
+ int rowLead = table.getSelectionModel().getLeadSelectionIndex();
+ int columnLead = table.getColumnModel().getSelectionModel().
+ getLeadSelectionIndex();
+ if (rowLead >= 0 && rowLead < rowCount && columnLead >= 0
+ && columnLead < columnCount)
+ {
+ Rectangle dirtyRect = table.getCellRect(rowLead, columnLead, false);
+ table.repaint(dirtyRect);
+ }
+ }
+ }
+
+ public class MouseInputHandler implements MouseInputListener
+ {
+ Point begin, curr;
+
+ private void updateSelection(boolean controlPressed)
+ {
+ // Update the rows
+ int lo_row = table.rowAtPoint(begin);
+ int hi_row = table.rowAtPoint(curr);
+ ListSelectionModel rowModel = table.getSelectionModel();
+ if (lo_row != -1 && hi_row != -1)
+ {
+ if (controlPressed && rowModel.getSelectionMode()
+ != ListSelectionModel.SINGLE_SELECTION)
+ rowModel.addSelectionInterval(lo_row, hi_row);
+ else
+ rowModel.setSelectionInterval(lo_row, hi_row);
+ }
+
+ // Update the columns
+ int lo_col = table.columnAtPoint(begin);
+ int hi_col = table.columnAtPoint(curr);
+ ListSelectionModel colModel = table.getColumnModel().
+ getSelectionModel();
+ if (lo_col != -1 && hi_col != -1)
+ {
+ if (controlPressed && colModel.getSelectionMode() !=
+ ListSelectionModel.SINGLE_SELECTION)
+ colModel.addSelectionInterval(lo_col, hi_col);
+ else
+ colModel.setSelectionInterval(lo_col, hi_col);
+ }
+ }
+
+ /**
+ * For the double click, start the cell editor.
+ */
+ public void mouseClicked(MouseEvent e)
+ {
+ Point p = e.getPoint();
+ int row = table.rowAtPoint(p);
+ int col = table.columnAtPoint(p);
+ if (table.isCellEditable(row, col))
+ {
+ // If the cell editor is the default editor, we request the
+ // number of the required clicks from it. Otherwise,
+ // require two clicks (double click).
+ TableCellEditor editor = table.getCellEditor(row, col);
+ if (editor instanceof DefaultCellEditor)
+ {
+ DefaultCellEditor ce = (DefaultCellEditor) editor;
+ if (e.getClickCount() < ce.getClickCountToStart())
+ return;
+ }
+ table.editCellAt(row, col);
+ }
+ }
+
+ public void mouseDragged(MouseEvent e)
+ {
+ if (table.isEnabled())
+ {
+ curr = new Point(e.getX(), e.getY());
+ updateSelection(e.isControlDown());
+ }
+ }
+
+ public void mouseEntered(MouseEvent e)
+ {
+ // Nothing to do here.
+ }
+
+ public void mouseExited(MouseEvent e)
+ {
+ // Nothing to do here.
+ }
+
+ public void mouseMoved(MouseEvent e)
+ {
+ // Nothing to do here.
+ }
+
+ public void mousePressed(MouseEvent e)
+ {
+ if (table.isEnabled())
+ {
+ ListSelectionModel rowModel = table.getSelectionModel();
+ ListSelectionModel colModel = table.getColumnModel().getSelectionModel();
+ int rowLead = rowModel.getLeadSelectionIndex();
+ int colLead = colModel.getLeadSelectionIndex();
+
+ begin = new Point(e.getX(), e.getY());
+ curr = new Point(e.getX(), e.getY());
+ //if control is pressed and the cell is already selected, deselect it
+ if (e.isControlDown() && table.isCellSelected(
+ table.rowAtPoint(begin), table.columnAtPoint(begin)))
+ {
+ table.getSelectionModel().
+ removeSelectionInterval(table.rowAtPoint(begin),
+ table.rowAtPoint(begin));
+ table.getColumnModel().getSelectionModel().
+ removeSelectionInterval(table.columnAtPoint(begin),
+ table.columnAtPoint(begin));
+ }
+ else
+ updateSelection(e.isControlDown());
+
+ // If we were editing, but the moved to another cell, stop editing
+ if (rowLead != rowModel.getLeadSelectionIndex() ||
+ colLead != colModel.getLeadSelectionIndex())
+ if (table.isEditing())
+ table.editingStopped(new ChangeEvent(e));
+
+ // Must request focus explicitly.
+ table.requestFocusInWindow();
+ }
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ if (table.isEnabled())
+ {
+ begin = null;
+ curr = null;
+ }
+ }
+ }
+
+ /**
+ * Listens for changes to the model property of the JTable and adjusts some
+ * settings.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+ private class PropertyChangeHandler implements PropertyChangeListener
+ {
+ /**
+ * Receives notification if one of the JTable's properties changes.
+ *
+ * @param ev the property change event
+ */
+ public void propertyChange(PropertyChangeEvent ev)
+ {
+ String propName = ev.getPropertyName();
+ if (propName.equals("model"))
+ {
+ ListSelectionModel rowSel = table.getSelectionModel();
+ rowSel.clearSelection();
+ ListSelectionModel colSel = table.getColumnModel().getSelectionModel();
+ colSel.clearSelection();
+ TableModel model = table.getModel();
+
+ // Adjust lead and anchor selection indices of the row and column
+ // selection models.
+ if (model.getRowCount() > 0)
+ {
+ rowSel.setAnchorSelectionIndex(0);
+ rowSel.setLeadSelectionIndex(0);
+ }
+ else
+ {
+ rowSel.setAnchorSelectionIndex(-1);
+ rowSel.setLeadSelectionIndex(-1);
+ }
+ if (model.getColumnCount() > 0)
+ {
+ colSel.setAnchorSelectionIndex(0);
+ colSel.setLeadSelectionIndex(0);
+ }
+ else
+ {
+ colSel.setAnchorSelectionIndex(-1);
+ colSel.setLeadSelectionIndex(-1);
+ }
+ }
+ }
+ }
+
+ protected FocusListener createFocusListener()
+ {
+ return new FocusHandler();
+ }
+
+ protected MouseInputListener createMouseInputListener()
+ {
+ return new MouseInputHandler();
+ }
+
+
+ /**
+ * Creates and returns a key listener for the JTable.
+ *
+ * @return a key listener for the JTable
+ */
+ protected KeyListener createKeyListener()
+ {
+ return new KeyHandler();
+ }
+
+ /**
+ * Return the maximum size of the table. The maximum height is the row
+ * height times the number of rows. The maximum width is the sum of
+ * the maximum widths of each column.
+ *
+ * @param comp the component whose maximum size is being queried,
+ * this is ignored.
+ * @return a Dimension object representing the maximum size of the table,
+ * or null if the table has no elements.
+ */
+ public Dimension getMaximumSize(JComponent comp)
+ {
+ int maxTotalColumnWidth = 0;
+ for (int i = 0; i < table.getColumnCount(); i++)
+ maxTotalColumnWidth += table.getColumnModel().getColumn(i).getMaxWidth();
+
+ return new Dimension(maxTotalColumnWidth, getHeight());
+ }
+
+ /**
+ * Return the minimum size of the table. The minimum height is the row
+ * height times the number of rows. The minimum width is the sum of
+ * the minimum widths of each column.
+ *
+ * @param comp the component whose minimum size is being queried,
+ * this is ignored.
+ * @return a Dimension object representing the minimum size of the table,
+ * or null if the table has no elements.
+ */
+ public Dimension getMinimumSize(JComponent comp)
+ {
+ int minTotalColumnWidth = 0;
+ for (int i = 0; i < table.getColumnCount(); i++)
+ minTotalColumnWidth += table.getColumnModel().getColumn(i).getMinWidth();
+
+ return new Dimension(minTotalColumnWidth, getHeight());
+ }
+
+ /**
+ * Returns the preferred size for the table of that UI.
+ *
+ * @param comp ignored, the <code>table</code> field is used instead
+ *
+ * @return the preferred size for the table of that UI
+ */
+ public Dimension getPreferredSize(JComponent comp)
+ {
+ int prefTotalColumnWidth = 0;
+ TableColumnModel tcm = table.getColumnModel();
+
+ for (int i = 0; i < tcm.getColumnCount(); i++)
+ {
+ TableColumn col = tcm.getColumn(i);
+ prefTotalColumnWidth += col.getPreferredWidth();
+ }
+
+ return new Dimension(prefTotalColumnWidth, getHeight());
+ }
+
+ /**
+ * Returns the table height. This helper method is used by
+ * {@link #getMinimumSize(JComponent)}, {@link #getPreferredSize(JComponent)}
+ * and {@link #getMaximumSize(JComponent)} to determine the table height.
+ *
+ * @return the table height
+ */
+ private int getHeight()
+ {
+ int height = 0;
+ int rowCount = table.getRowCount();
+ if (rowCount > 0 && table.getColumnCount() > 0)
+ {
+ Rectangle r = table.getCellRect(rowCount - 1, 0, true);
+ height = r.y + r.height;
+ }
+ return height;
+ }
+
+ protected void installDefaults()
+ {
+ LookAndFeel.installColorsAndFont(table, "Table.background",
+ "Table.foreground", "Table.font");
+ table.setGridColor(UIManager.getColor("Table.gridColor"));
+ table.setSelectionForeground(UIManager.getColor("Table.selectionForeground"));
+ table.setSelectionBackground(UIManager.getColor("Table.selectionBackground"));
+ table.setOpaque(true);
+ }
+
+ /**
+ * Installs keyboard actions on the table.
+ */
+ protected void installKeyboardActions()
+ {
+ // Install the input map.
+ InputMap inputMap =
+ (InputMap) SharedUIDefaults.get("Table.ancestorInputMap");
+ SwingUtilities.replaceUIInputMap(table,
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
+ inputMap);
+
+ // FIXME: The JDK uses a LazyActionMap for parentActionMap
+ SwingUtilities.replaceUIActionMap(table, getActionMap());
+
+ }
+
+ /**
+ * Fetches the action map from the UI defaults, or create a new one
+ * if the action map hasn't been initialized.
+ *
+ * @return the action map
+ */
+ private ActionMap getActionMap()
+ {
+ ActionMap am = (ActionMap) UIManager.get("Table.actionMap");
+ if (am == null)
+ {
+ am = createDefaultActions();
+ UIManager.getLookAndFeelDefaults().put("Table.actionMap", am);
+ }
+ return am;
+ }
+
+ private ActionMap createDefaultActions()
+ {
+ ActionMapUIResource am = new ActionMapUIResource();
+ Action action = new TableAction();
+
+ am.put("cut", TransferHandler.getCutAction());
+ am.put("copy", TransferHandler.getCopyAction());
+ am.put("paste", TransferHandler.getPasteAction());
+
+ am.put("cancel", action);
+ am.put("selectAll", action);
+ am.put("clearSelection", action);
+ am.put("startEditing", action);
+
+ am.put("selectNextRow", action);
+ am.put("selectNextRowCell", action);
+ am.put("selectNextRowExtendSelection", action);
+ am.put("selectNextRowChangeLead", action);
+
+ am.put("selectPreviousRow", action);
+ am.put("selectPreviousRowCell", action);
+ am.put("selectPreviousRowExtendSelection", action);
+ am.put("selectPreviousRowChangeLead", action);
+
+ am.put("selectNextColumn", action);
+ am.put("selectNextColumnCell", action);
+ am.put("selectNextColumnExtendSelection", action);
+ am.put("selectNextColumnChangeLead", action);
+
+ am.put("selectPreviousColumn", action);
+ am.put("selectPreviousColumnCell", action);
+ am.put("selectPreviousColumnExtendSelection", action);
+ am.put("selectPreviousColumnChangeLead", action);
+
+ am.put("scrollLeftChangeSelection", action);
+ am.put("scrollLeftExtendSelection", action);
+ am.put("scrollRightChangeSelection", action);
+ am.put("scrollRightExtendSelection", action);
+
+ am.put("scrollUpChangeSelection", action);
+ am.put("scrollUpExtendSelection", action);
+ am.put("scrollDownChangeSelection", action);
+ am.put("scrolldownExtendSelection", action);
+
+ am.put("selectFirstColumn", action);
+ am.put("selectFirstColumnExtendSelection", action);
+ am.put("selectLastColumn", action);
+ am.put("selectLastColumnExtendSelection", action);
+
+ am.put("selectFirstRow", action);
+ am.put("selectFirstRowExtendSelection", action);
+ am.put("selectLastRow", action);
+ am.put("selectLastRowExtendSelection", action);
+
+ am.put("addToSelection", action);
+ am.put("toggleAndAnchor", action);
+ am.put("extendTo", action);
+ am.put("moveSelectionTo", action);
+
+ return am;
+ }
+
+ /**
+ * This class implements the actions that we want to happen
+ * when specific keys are pressed for the JTable. The actionPerformed
+ * method is called when a key that has been registered for the JTable
+ * is received.
+ */
+ private static class TableAction
+ extends AbstractAction
+ {
+ /**
+ * What to do when this action is called.
+ *
+ * @param e the ActionEvent that caused this action.
+ */
+ public void actionPerformed(ActionEvent e)
+ {
+ JTable table = (JTable) e.getSource();
+
+ DefaultListSelectionModel rowModel
+ = (DefaultListSelectionModel) table.getSelectionModel();
+ DefaultListSelectionModel colModel
+ = (DefaultListSelectionModel) table.getColumnModel().getSelectionModel();
+
+ int rowLead = rowModel.getLeadSelectionIndex();
+ int rowMax = table.getModel().getRowCount() - 1;
+
+ int colLead = colModel.getLeadSelectionIndex();
+ int colMax = table.getModel().getColumnCount() - 1;
+
+ // The command with which the action has been called is stored
+ // in this undocumented action value. This allows us to have only
+ // one Action instance to serve all keyboard input for JTable.
+ String command = (String) getValue("__command__");
+ if (command.equals("selectPreviousRowExtendSelection"))
+ {
+ rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0));
+ }
+ else if (command.equals("selectLastColumn"))
+ {
+ colModel.setSelectionInterval(colMax, colMax);
+ }
+ else if (command.equals("startEditing"))
+ {
+ if (table.isCellEditable(rowLead, colLead))
+ table.editCellAt(rowLead, colLead);
+ }
+ else if (command.equals("selectFirstRowExtendSelection"))
+ {
+ rowModel.setLeadSelectionIndex(0);
+ }
+ else if (command.equals("selectFirstColumn"))
+ {
+ colModel.setSelectionInterval(0, 0);
+ }
+ else if (command.equals("selectFirstColumnExtendSelection"))
+ {
+ colModel.setLeadSelectionIndex(0);
+ }
+ else if (command.equals("selectLastRow"))
+ {
+ rowModel.setSelectionInterval(rowMax, rowMax);
+ }
+ else if (command.equals("selectNextRowExtendSelection"))
+ {
+ rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
+ }
+ else if (command.equals("selectFirstRow"))
+ {
+ rowModel.setSelectionInterval(0, 0);
+ }
+ else if (command.equals("selectNextColumnExtendSelection"))
+ {
+ colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax));
+ }
+ else if (command.equals("selectLastColumnExtendSelection"))
+ {
+ colModel.setLeadSelectionIndex(colMax);
+ }
+ else if (command.equals("selectPreviousColumnExtendSelection"))
+ {
+ colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0));
+ }
+ else if (command.equals("selectNextRow"))
+ {
+ rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
+ Math.min(rowLead + 1, rowMax));
+ }
+ else if (command.equals("scrollUpExtendSelection"))
+ {
+ int target;
+ if (rowLead == getFirstVisibleRowIndex(table))
+ target = Math.max(0, rowLead - (getLastVisibleRowIndex(table)
+ - getFirstVisibleRowIndex(table) + 1));
+ else
+ target = getFirstVisibleRowIndex(table);
+
+ rowModel.setLeadSelectionIndex(target);
+ colModel.setLeadSelectionIndex(colLead);
+ }
+ else if (command.equals("selectPreviousRow"))
+ {
+ rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
+ Math.max(rowLead - 1, 0));
+ }
+ else if (command.equals("scrollRightChangeSelection"))
+ {
+ int target;
+ if (colLead == getLastVisibleColumnIndex(table))
+ target = Math.min(colMax, colLead
+ + (getLastVisibleColumnIndex(table)
+ - getFirstVisibleColumnIndex(table) + 1));
+ else
+ target = getLastVisibleColumnIndex(table);
+
+ colModel.setSelectionInterval(target, target);
+ rowModel.setSelectionInterval(rowLead, rowLead);
+ }
+ else if (command.equals("selectPreviousColumn"))
+ {
+ colModel.setSelectionInterval(Math.max(colLead - 1, 0),
+ Math.max(colLead - 1, 0));
+ }
+ else if (command.equals("scrollLeftChangeSelection"))
+ {
+ int target;
+ if (colLead == getFirstVisibleColumnIndex(table))
+ target = Math.max(0, colLead - (getLastVisibleColumnIndex(table)
+ - getFirstVisibleColumnIndex(table) + 1));
+ else
+ target = getFirstVisibleColumnIndex(table);
+
+ colModel.setSelectionInterval(target, target);
+ rowModel.setSelectionInterval(rowLead, rowLead);
+ }
+ else if (command.equals("clearSelection"))
+ {
+ table.clearSelection();
+ }
+ else if (command.equals("cancel"))
+ {
+ // FIXME: implement other parts of "cancel" like undo-ing last
+ // selection. Right now it just calls editingCancelled if
+ // we're currently editing.
+ if (table.isEditing())
+ table.editingCanceled(new ChangeEvent("cancel"));
+ }
+ else if (command.equals("selectNextRowCell")
+ || command.equals("selectPreviousRowCell")
+ || command.equals("selectNextColumnCell")
+ || command.equals("selectPreviousColumnCell"))
+ {
+ // If nothing is selected, select the first cell in the table
+ if (table.getSelectedRowCount() == 0 &&
+ table.getSelectedColumnCount() == 0)
+ {
+ rowModel.setSelectionInterval(0, 0);
+ colModel.setSelectionInterval(0, 0);
+ return;
+ }
+
+ // If the lead selection index isn't selected (ie a remove operation
+ // happened, then set the lead to the first selected cell in the
+ // table
+ if (!table.isCellSelected(rowLead, colLead))
+ {
+ rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(),
+ rowModel.getMinSelectionIndex());
+ colModel.addSelectionInterval(colModel.getMinSelectionIndex(),
+ colModel.getMinSelectionIndex());
+ return;
+ }
+
+ // multRowsSelected and multColsSelected tell us if multiple rows or
+ // columns are selected, respectively
+ boolean multRowsSelected, multColsSelected;
+ multRowsSelected = table.getSelectedRowCount() > 1 &&
+ table.getRowSelectionAllowed();
+
+ multColsSelected = table.getSelectedColumnCount() > 1 &&
+ table.getColumnSelectionAllowed();
+
+ // If there is just one selection, select the next cell, and wrap
+ // when you get to the edges of the table.
+ if (!multColsSelected && !multRowsSelected)
+ {
+ if (command.indexOf("Column") != -1)
+ advanceSingleSelection(colModel, colMax, rowModel, rowMax,
+ command.equals("selectPreviousColumnCell"));
+ else
+ advanceSingleSelection(rowModel, rowMax, colModel, colMax,
+ command.equals("selectPreviousRowCell"));
+ return;
+ }
+
+
+ // rowMinSelected and rowMaxSelected are the minimum and maximum
+ // values respectively of selected cells in the row selection model
+ // Similarly for colMinSelected and colMaxSelected.
+ int rowMaxSelected = table.getRowSelectionAllowed() ?
+ rowModel.getMaxSelectionIndex() : table.getModel().getRowCount() - 1;
+ int rowMinSelected = table.getRowSelectionAllowed() ?
+ rowModel.getMinSelectionIndex() : 0;
+ int colMaxSelected = table.getColumnSelectionAllowed() ?
+ colModel.getMaxSelectionIndex() :
+ table.getModel().getColumnCount() - 1;
+ int colMinSelected = table.getColumnSelectionAllowed() ?
+ colModel.getMinSelectionIndex() : 0;
+
+ // If there are multiple rows and columns selected, select the next
+ // cell and wrap at the edges of the selection.
+ if (command.indexOf("Column") != -1)
+ advanceMultipleSelection(table, colModel, colMinSelected,
+ colMaxSelected, rowModel, rowMinSelected,
+ rowMaxSelected,
+ command.equals("selectPreviousColumnCell"),
+ true);
+
+ else
+ advanceMultipleSelection(table, rowModel, rowMinSelected,
+ rowMaxSelected, colModel, colMinSelected,
+ colMaxSelected,
+ command.equals("selectPreviousRowCell"),
+ false);
+ }
+ else if (command.equals("selectNextColumn"))
+ {
+ colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
+ Math.min(colLead + 1, colMax));
+ }
+ else if (command.equals("scrollLeftExtendSelection"))
+ {
+ int target;
+ if (colLead == getFirstVisibleColumnIndex(table))
+ target = Math.max(0, colLead - (getLastVisibleColumnIndex(table)
+ - getFirstVisibleColumnIndex(table) + 1));
+ else
+ target = getFirstVisibleColumnIndex(table);
+
+ colModel.setLeadSelectionIndex(target);
+ rowModel.setLeadSelectionIndex(rowLead);
+ }
+ else if (command.equals("scrollDownChangeSelection"))
+ {
+ int target;
+ if (rowLead == getLastVisibleRowIndex(table))
+ target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table)
+ - getFirstVisibleRowIndex(table) + 1));
+ else
+ target = getLastVisibleRowIndex(table);
+
+ rowModel.setSelectionInterval(target, target);
+ colModel.setSelectionInterval(colLead, colLead);
+ }
+ else if (command.equals("scrollRightExtendSelection"))
+ {
+ int target;
+ if (colLead == getLastVisibleColumnIndex(table))
+ target = Math.min(colMax, colLead + (getLastVisibleColumnIndex(table)
+ - getFirstVisibleColumnIndex(table) + 1));
+ else
+ target = getLastVisibleColumnIndex(table);
+
+ colModel.setLeadSelectionIndex(target);
+ rowModel.setLeadSelectionIndex(rowLead);
+ }
+ else if (command.equals("selectAll"))
+ {
+ table.selectAll();
+ }
+ else if (command.equals("selectLastRowExtendSelection"))
+ {
+ rowModel.setLeadSelectionIndex(rowMax);
+ colModel.setLeadSelectionIndex(colLead);
+ }
+ else if (command.equals("scrollDownExtendSelection"))
+ {
+ int target;
+ if (rowLead == getLastVisibleRowIndex(table))
+ target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table)
+ - getFirstVisibleRowIndex(table) + 1));
+ else
+ target = getLastVisibleRowIndex(table);
+
+ rowModel.setLeadSelectionIndex(target);
+ colModel.setLeadSelectionIndex(colLead);
+ }
+ else if (command.equals("scrollUpChangeSelection"))
+ {
+ int target;
+ if (rowLead == getFirstVisibleRowIndex(table))
+ target = Math.max(0, rowLead - (getLastVisibleRowIndex(table)
+ - getFirstVisibleRowIndex(table) + 1));
+ else
+ target = getFirstVisibleRowIndex(table);
+
+ rowModel.setSelectionInterval(target, target);
+ colModel.setSelectionInterval(colLead, colLead);
+ }
+ else if (command.equals("selectNextRowChangeLead"))
+ {
+ if (rowModel.getSelectionMode()
+ != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
+ {
+ // just "selectNextRow"
+ rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
+ Math.min(rowLead + 1, rowMax));
+ colModel.setSelectionInterval(colLead, colLead);
+ }
+ else
+ rowModel.moveLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
+ }
+ else if (command.equals("selectPreviousRowChangeLead"))
+ {
+ if (rowModel.getSelectionMode()
+ != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
+ {
+ // just selectPreviousRow
+ rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
+ Math.min(rowLead - 1, 0));
+ colModel.setSelectionInterval(colLead, colLead);
+ }
+ else
+ rowModel.moveLeadSelectionIndex(Math.max(rowLead - 1, 0));
+ }
+ else if (command.equals("selectNextColumnChangeLead"))
+ {
+ if (colModel.getSelectionMode()
+ != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
+ {
+ // just selectNextColumn
+ rowModel.setSelectionInterval(rowLead, rowLead);
+ colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
+ Math.min(colLead + 1, colMax));
+ }
+ else
+ colModel.moveLeadSelectionIndex(Math.min(colLead + 1, colMax));
+ }
+ else if (command.equals("selectPreviousColumnChangeLead"))
+ {
+ if (colModel.getSelectionMode()
+ != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
+ {
+ // just selectPreviousColumn
+ rowModel.setSelectionInterval(rowLead, rowLead);
+ colModel.setSelectionInterval(Math.max(colLead - 1, 0),
+ Math.max(colLead - 1, 0));
+
+ }
+ else
+ colModel.moveLeadSelectionIndex(Math.max(colLead - 1, 0));
+ }
+ else if (command.equals("addToSelection"))
+ {
+ if (!table.isEditing())
+ {
+ int oldRowAnchor = rowModel.getAnchorSelectionIndex();
+ int oldColAnchor = colModel.getAnchorSelectionIndex();
+ rowModel.addSelectionInterval(rowLead, rowLead);
+ colModel.addSelectionInterval(colLead, colLead);
+ rowModel.setAnchorSelectionIndex(oldRowAnchor);
+ colModel.setAnchorSelectionIndex(oldColAnchor);
+ }
+ }
+ else if (command.equals("extendTo"))
+ {
+ rowModel.setSelectionInterval(rowModel.getAnchorSelectionIndex(),
+ rowLead);
+ colModel.setSelectionInterval(colModel.getAnchorSelectionIndex(),
+ colLead);
+ }
+ else if (command.equals("toggleAndAnchor"))
+ {
+ if (rowModel.isSelectedIndex(rowLead))
+ rowModel.removeSelectionInterval(rowLead, rowLead);
+ else
+ rowModel.addSelectionInterval(rowLead, rowLead);
+
+ if (colModel.isSelectedIndex(colLead))
+ colModel.removeSelectionInterval(colLead, colLead);
+ else
+ colModel.addSelectionInterval(colLead, colLead);
+
+ rowModel.setAnchorSelectionIndex(rowLead);
+ colModel.setAnchorSelectionIndex(colLead);
+ }
+ else if (command.equals("stopEditing"))
+ {
+ table.editingStopped(new ChangeEvent(command));
+ }
+ else
+ {
+ // If we're here that means we bound this TableAction class
+ // to a keyboard input but we either want to ignore that input
+ // or we just haven't implemented its action yet.
+
+ // Uncomment the following line to print the names of unused bindings
+ // when their keys are pressed
+
+ // System.out.println ("not implemented: "+e.getActionCommand());
+ }
+
+ // Any commands whose keyStrokes should be used by the Editor should not
+ // cause editing to be stopped: ie, the SPACE sends "addToSelection" but
+ // if the table is in editing mode, the space should not cause us to stop
+ // editing because it should be used by the Editor.
+ if (table.isEditing() && command != "startEditing"
+ && command != "addToSelection")
+ table.editingStopped(new ChangeEvent("update"));
+
+ table.scrollRectToVisible(table.getCellRect(
+ rowModel.getLeadSelectionIndex(), colModel.getLeadSelectionIndex(),
+ false));
+ }
+
+ /**
+ * Returns the column index of the first visible column.
+ * @return the column index of the first visible column.
+ */
+ int getFirstVisibleColumnIndex(JTable table)
+ {
+ ComponentOrientation or = table.getComponentOrientation();
+ Rectangle r = table.getVisibleRect();
+ if (!or.isLeftToRight())
+ r.translate((int) r.getWidth() - 1, 0);
+ return table.columnAtPoint(r.getLocation());
+ }
+
+ /**
+ * Returns the column index of the last visible column.
+ *
+ */
+ int getLastVisibleColumnIndex(JTable table)
+ {
+ ComponentOrientation or = table.getComponentOrientation();
+ Rectangle r = table.getVisibleRect();
+ if (or.isLeftToRight())
+ r.translate((int) r.getWidth() - 1, 0);
+ return table.columnAtPoint(r.getLocation());
+ }
+
+ /**
+ * Returns the row index of the first visible row.
+ *
+ */
+ int getFirstVisibleRowIndex(JTable table)
+ {
+ ComponentOrientation or = table.getComponentOrientation();
+ Rectangle r = table.getVisibleRect();
+ if (!or.isLeftToRight())
+ r.translate((int) r.getWidth() - 1, 0);
+ return table.rowAtPoint(r.getLocation());
+ }
+
+ /**
+ * Returns the row index of the last visible row.
+ *
+ */
+ int getLastVisibleRowIndex(JTable table)
+ {
+ ComponentOrientation or = table.getComponentOrientation();
+ Rectangle r = table.getVisibleRect();
+ r.translate(0, (int) r.getHeight() - 1);
+ if (or.isLeftToRight())
+ r.translate((int) r.getWidth() - 1, 0);
+ // The next if makes sure that we don't return -1 simply because
+ // there is white space at the bottom of the table (ie, the display
+ // area is larger than the table)
+ if (table.rowAtPoint(r.getLocation()) == -1)
+ {
+ if (getFirstVisibleRowIndex(table) == -1)
+ return -1;
+ else
+ return table.getModel().getRowCount() - 1;
+ }
+ return table.rowAtPoint(r.getLocation());
+ }
+
+ /**
+ * A helper method for the key bindings. Used because the actions
+ * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
+ *
+ * Selects the next (previous if SHIFT pressed) column for TAB, or row for
+ * ENTER from within the currently selected cells.
+ *
+ * @param firstModel the ListSelectionModel for columns (TAB) or
+ * rows (ENTER)
+ * @param firstMin the first selected index in firstModel
+ * @param firstMax the last selected index in firstModel
+ * @param secondModel the ListSelectionModel for rows (TAB) or
+ * columns (ENTER)
+ * @param secondMin the first selected index in secondModel
+ * @param secondMax the last selected index in secondModel
+ * @param reverse true if shift was held for the event
+ * @param eventIsTab true if TAB was pressed, false if ENTER pressed
+ */
+ void advanceMultipleSelection(JTable table, ListSelectionModel firstModel,
+ int firstMin,
+ int firstMax, ListSelectionModel secondModel,
+ int secondMin, int secondMax, boolean reverse,
+ boolean eventIsTab)
+ {
+ // If eventIsTab, all the "firsts" correspond to columns, otherwise, to
+ // rows "seconds" correspond to the opposite
+ int firstLead = firstModel.getLeadSelectionIndex();
+ int secondLead = secondModel.getLeadSelectionIndex();
+ int numFirsts = eventIsTab ?
+ table.getModel().getColumnCount() : table.getModel().getRowCount();
+ int numSeconds = eventIsTab ?
+ table.getModel().getRowCount() : table.getModel().getColumnCount();
+
+ // check if we have to wrap the "firsts" around, going to the other side
+ if ((firstLead == firstMax && !reverse) ||
+ (reverse && firstLead == firstMin))
+ {
+ firstModel.addSelectionInterval(reverse ? firstMax : firstMin,
+ reverse ? firstMax : firstMin);
+
+ // check if we have to wrap the "seconds"
+ if ((secondLead == secondMax && !reverse) ||
+ (reverse && secondLead == secondMin))
+ secondModel.addSelectionInterval(reverse ? secondMax : secondMin,
+ reverse ? secondMax : secondMin);
+
+ // if we're not wrapping the seconds, we have to find out where we
+ // are within the secondModel and advance to the next cell (or
+ // go back to the previous cell if reverse == true)
+ else
+ {
+ int[] secondsSelected;
+ if (eventIsTab && table.getRowSelectionAllowed() ||
+ !eventIsTab && table.getColumnSelectionAllowed())
+ secondsSelected = eventIsTab ?
+ table.getSelectedRows() : table.getSelectedColumns();
+ else
+ {
+ // if row selection is not allowed, then the entire column gets
+ // selected when you click on it, so consider ALL rows selected
+ secondsSelected = new int[numSeconds];
+ for (int i = 0; i < numSeconds; i++)
+ secondsSelected[i] = i;
+ }
+
+ // and now find the "next" index within the model
+ int secondIndex = reverse ? secondsSelected.length - 1 : 0;
+ if (!reverse)
+ while (secondsSelected[secondIndex] <= secondLead)
+ secondIndex++;
+ else
+ while (secondsSelected[secondIndex] >= secondLead)
+ secondIndex--;
+
+ // and select it - updating the lead selection index
+ secondModel.addSelectionInterval(secondsSelected[secondIndex],
+ secondsSelected[secondIndex]);
+ }
+ }
+ // We didn't have to wrap the firsts, so just find the "next" first
+ // and select it, we don't have to change "seconds"
+ else
+ {
+ int[] firstsSelected;
+ if (eventIsTab && table.getColumnSelectionAllowed() ||
+ !eventIsTab && table.getRowSelectionAllowed())
+ firstsSelected = eventIsTab ?
+ table.getSelectedColumns() : table.getSelectedRows();
+ else
+ {
+ // if selection not allowed, consider ALL firsts to be selected
+ firstsSelected = new int[numFirsts];
+ for (int i = 0; i < numFirsts; i++)
+ firstsSelected[i] = i;
+ }
+ int firstIndex = reverse ? firstsSelected.length - 1 : 0;
+ if (!reverse)
+ while (firstsSelected[firstIndex] <= firstLead)
+ firstIndex++;
+ else
+ while (firstsSelected[firstIndex] >= firstLead)
+ firstIndex--;
+ firstModel.addSelectionInterval(firstsSelected[firstIndex],
+ firstsSelected[firstIndex]);
+ secondModel.addSelectionInterval(secondLead, secondLead);
+ }
+ }
+
+ /**
+ * A helper method for the key bindings. Used because the actions
+ * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
+ *
+ * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER)
+ * in the table, changing the current selection. All cells in the table
+ * are eligible, not just the ones that are currently selected.
+ * @param firstModel the ListSelectionModel for columns (TAB) or rows
+ * (ENTER)
+ * @param firstMax the last index in firstModel
+ * @param secondModel the ListSelectionModel for rows (TAB) or columns
+ * (ENTER)
+ * @param secondMax the last index in secondModel
+ * @param reverse true if SHIFT was pressed for the event
+ */
+
+ void advanceSingleSelection(ListSelectionModel firstModel, int firstMax,
+ ListSelectionModel secondModel, int secondMax,
+ boolean reverse)
+ {
+ // for TABs, "first" corresponds to columns and "seconds" to rows.
+ // the opposite is true for ENTERs
+ int firstLead = firstModel.getLeadSelectionIndex();
+ int secondLead = secondModel.getLeadSelectionIndex();
+
+ // if we are going backwards subtract 2 because we later add 1
+ // for a net change of -1
+ if (reverse && (firstLead == 0))
+ {
+ // check if we have to wrap around
+ if (secondLead == 0)
+ secondLead += secondMax + 1;
+ secondLead -= 2;
+ }
+
+ // do we have to wrap the "seconds"?
+ if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax))
+ secondModel.setSelectionInterval((secondLead + 1) % (secondMax + 1),
+ (secondLead + 1) % (secondMax + 1));
+ // if not, just reselect the current lead
+ else
+ secondModel.setSelectionInterval(secondLead, secondLead);
+
+ // if we are going backwards, subtract 2 because we add 1 later
+ // for net change of -1
+ if (reverse)
+ {
+ // check for wraparound
+ if (firstLead == 0)
+ firstLead += firstMax + 1;
+ firstLead -= 2;
+ }
+ // select the next "first"
+ firstModel.setSelectionInterval((firstLead + 1) % (firstMax + 1),
+ (firstLead + 1) % (firstMax + 1));
+ }
+ }
+
+ protected void installListeners()
+ {
+ if (focusListener == null)
+ focusListener = createFocusListener();
+ table.addFocusListener(focusListener);
+ if (keyListener == null)
+ keyListener = createKeyListener();
+ table.addKeyListener(keyListener);
+ if (mouseInputListener == null)
+ mouseInputListener = createMouseInputListener();
+ table.addMouseListener(mouseInputListener);
+ table.addMouseMotionListener(mouseInputListener);
+ if (propertyChangeListener == null)
+ propertyChangeListener = new PropertyChangeHandler();
+ table.addPropertyChangeListener(propertyChangeListener);
+ }
+
+ /**
+ * Uninstalls UI defaults that have been installed by
+ * {@link #installDefaults()}.
+ */
+ protected void uninstallDefaults()
+ {
+ // Nothing to do here for now.
+ }
+
+ /**
+ * Uninstalls the keyboard actions that have been installed by
+ * {@link #installKeyboardActions()}.
+ */
+ protected void uninstallKeyboardActions()
+ {
+ SwingUtilities.replaceUIInputMap(table, JComponent.
+ WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
+ SwingUtilities.replaceUIActionMap(table, null);
+ }
+
+ protected void uninstallListeners()
+ {
+ table.removeFocusListener(focusListener);
+ table.removeKeyListener(keyListener);
+ table.removeMouseListener(mouseInputListener);
+ table.removeMouseMotionListener(mouseInputListener);
+ table.removePropertyChangeListener(propertyChangeListener);
+ propertyChangeListener = null;
+ }
+
+ public void installUI(JComponent comp)
+ {
+ table = (JTable) comp;
+ rendererPane = new CellRendererPane();
+ table.add(rendererPane);
+
+ installDefaults();
+ installKeyboardActions();
+ installListeners();
+ }
+
+ public void uninstallUI(JComponent c)
+ {
+ uninstallListeners();
+ uninstallKeyboardActions();
+ uninstallDefaults();
+
+ table.remove(rendererPane);
+ rendererPane = null;
+ table = null;
+ }
+
+ /**
+ * Paints a single cell in the table.
+ *
+ * @param g The graphics context to paint in
+ * @param row The row number to paint
+ * @param col The column number to paint
+ * @param bounds The bounds of the cell to paint, assuming a coordinate
+ * system beginning at <code>(0,0)</code> in the upper left corner of the
+ * table
+ * @param rend A cell renderer to paint with
+ */
+ void paintCell(Graphics g, int row, int col, Rectangle bounds,
+ TableCellRenderer rend)
+ {
+ Component comp = table.prepareRenderer(rend, row, col);
+ rendererPane.paintComponent(g, comp, table, bounds);
+ }
+
+ /**
+ * Paint the associated table.
+ */
+ public void paint(Graphics gfx, JComponent ignored)
+ {
+ int ncols = table.getColumnCount();
+ int nrows = table.getRowCount();
+ if (nrows == 0 || ncols == 0)
+ return;
+
+ Rectangle clip = gfx.getClipBounds();
+
+ // Determine the range of cells that are within the clip bounds.
+ Point p1 = new Point(clip.x, clip.y);
+ int c0 = table.columnAtPoint(p1);
+ if (c0 == -1)
+ c0 = 0;
+ int r0 = table.rowAtPoint(p1);
+ if (r0 == -1)
+ r0 = 0;
+ Point p2 = new Point(clip.x + clip.width, clip.y + clip.height);
+ int cn = table.columnAtPoint(p2);
+ if (cn == -1)
+ cn = table.getColumnCount() - 1;
+ int rn = table.rowAtPoint(p2);
+ if (rn == -1)
+ rn = table.getRowCount() - 1;
+
+ int columnMargin = table.getColumnModel().getColumnMargin();
+ int rowMargin = table.getRowMargin();
+
+ TableColumnModel cmodel = table.getColumnModel();
+ int[] widths = new int[cn + 1];
+ for (int i = c0; i <= cn; i++)
+ {
+ widths[i] = cmodel.getColumn(i).getWidth() - columnMargin;
+ }
+
+ Rectangle bounds = table.getCellRect(r0, c0, false);
+ // The left boundary of the area being repainted.
+ int left = bounds.x;
+
+ // The top boundary of the area being repainted.
+ int top = bounds.y;
+
+ // The bottom boundary of the area being repainted.
+ int bottom;
+
+ // paint the cell contents
+ Color grid = table.getGridColor();
+ for (int r = r0; r <= rn; ++r)
+ {
+ for (int c = c0; c <= cn; ++c)
+ {
+ bounds.width = widths[c];
+ paintCell(gfx, r, c, bounds, table.getCellRenderer(r, c));
+ bounds.x += widths[c] + columnMargin;
+ }
+ bounds.x = left;
+ bounds.y += table.getRowHeight(r);
+ // Update row height for tables with custom heights.
+ bounds.height = table.getRowHeight(r + 1) - rowMargin;
+ }
+
+ bottom = bounds.y - rowMargin;
+
+ // paint vertical grid lines
+ if (grid != null && table.getShowVerticalLines())
+ {
+ Color save = gfx.getColor();
+ gfx.setColor(grid);
+ int x = left - columnMargin;
+ for (int c = c0; c <= cn; ++c)
+ {
+ // The vertical grid is draw right from the cells, so we
+ // add before drawing.
+ x += widths[c] + columnMargin;
+ gfx.drawLine(x, top, x, bottom);
+ }
+ gfx.setColor(save);
+ }
+
+ // paint horizontal grid lines
+ if (grid != null && table.getShowHorizontalLines())
+ {
+ Color save = gfx.getColor();
+ gfx.setColor(grid);
+ int y = top - rowMargin;
+ for (int r = r0; r <= rn; ++r)
+ {
+ // The horizontal grid is draw below the cells, so we
+ // add before drawing.
+ y += table.getRowHeight(r);
+ gfx.drawLine(left, y, p2.x, y);
+ }
+ gfx.setColor(save);
+ }
+ }
+}