001/* BasicTableUI.java --
002   Copyright (C) 2004 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.plaf.basic;
040
041import java.awt.Color;
042import java.awt.Component;
043import java.awt.ComponentOrientation;
044import java.awt.Dimension;
045import java.awt.Graphics;
046import java.awt.Point;
047import java.awt.Rectangle;
048import java.awt.event.ActionEvent;
049import java.awt.event.FocusEvent;
050import java.awt.event.FocusListener;
051import java.awt.event.KeyEvent;
052import java.awt.event.KeyListener;
053import java.awt.event.MouseEvent;
054import java.beans.PropertyChangeEvent;
055import java.beans.PropertyChangeListener;
056
057import javax.swing.AbstractAction;
058import javax.swing.Action;
059import javax.swing.ActionMap;
060import javax.swing.CellRendererPane;
061import javax.swing.DefaultCellEditor;
062import javax.swing.DefaultListSelectionModel;
063import javax.swing.InputMap;
064import javax.swing.JComponent;
065import javax.swing.JTable;
066import javax.swing.ListSelectionModel;
067import javax.swing.LookAndFeel;
068import javax.swing.SwingUtilities;
069import javax.swing.TransferHandler;
070import javax.swing.UIManager;
071import javax.swing.border.Border;
072import javax.swing.event.ChangeEvent;
073import javax.swing.event.MouseInputListener;
074import javax.swing.plaf.ActionMapUIResource;
075import javax.swing.plaf.ComponentUI;
076import javax.swing.plaf.TableUI;
077import javax.swing.table.TableCellEditor;
078import javax.swing.table.TableCellRenderer;
079import javax.swing.table.TableColumn;
080import javax.swing.table.TableColumnModel;
081import javax.swing.table.TableModel;
082
083public class BasicTableUI extends TableUI
084{
085  public static ComponentUI createUI(JComponent comp) 
086  {
087    return new BasicTableUI();
088  }
089
090  protected FocusListener focusListener;  
091  protected KeyListener keyListener;   
092  protected MouseInputListener  mouseInputListener;   
093  protected CellRendererPane rendererPane;   
094  protected JTable table;
095
096  /** The normal cell border. */
097  Border cellBorder;
098
099  /** The action bound to KeyStrokes. */
100  TableAction action;
101
102  /**
103   * Listens for changes to the tables properties.
104   */
105  private PropertyChangeListener propertyChangeListener;
106
107  /**
108   * Handles key events for the JTable. Key events should be handled through
109   * the InputMap/ActionMap mechanism since JDK1.3. This class is only there
110   * for backwards compatibility.
111   * 
112   * @author Roman Kennke (kennke@aicas.com)
113   */
114  public class KeyHandler implements KeyListener
115  {
116
117    /**
118     * Receives notification that a key has been pressed and released.
119     * Activates the editing session for the focused cell by pressing the
120     * character keys.
121     *
122     * @param event the key event
123     */
124    public void keyTyped(KeyEvent event)
125    {
126      // Key events should be handled through the InputMap/ActionMap mechanism
127      // since JDK1.3. This class is only there for backwards compatibility.
128      
129      // Editor activation is a specific kind of response to ''any''
130      // character key. Hence it is handled here.
131      if (!table.isEditing() && table.isEnabled())
132        {
133          int r = table.getSelectedRow();
134          int c = table.getSelectedColumn();
135          if (table.isCellEditable(r, c))
136            table.editCellAt(r, c);
137        }
138    }
139
140    /**
141     * Receives notification that a key has been pressed.
142     *
143     * @param event the key event
144     */
145    public void keyPressed(KeyEvent event)
146    {
147      // Key events should be handled through the InputMap/ActionMap mechanism
148      // since JDK1.3. This class is only there for backwards compatibility.
149    }
150
151    /**
152     * Receives notification that a key has been released.
153     *
154     * @param event the key event
155     */
156    public void keyReleased(KeyEvent event)
157    {
158      // Key events should be handled through the InputMap/ActionMap mechanism
159      // since JDK1.3. This class is only there for backwards compatibility.
160    }
161  }
162
163  public class FocusHandler implements FocusListener
164  {
165    public void focusGained(FocusEvent e)
166    {
167      // The only thing that is affected by a focus change seems to be
168      // how the lead cell is painted. So we repaint this cell.
169      repaintLeadCell();
170    }
171
172    public void focusLost(FocusEvent e)
173    {
174      // The only thing that is affected by a focus change seems to be
175      // how the lead cell is painted. So we repaint this cell.
176      repaintLeadCell();
177    }
178
179    /**
180     * Repaints the lead cell in response to a focus change, to refresh
181     * the display of the focus indicator.
182     */
183    private void repaintLeadCell()
184    {
185      int rowCount = table.getRowCount();
186      int columnCount = table.getColumnCount();
187      int rowLead = table.getSelectionModel().getLeadSelectionIndex();
188      int columnLead = table.getColumnModel().getSelectionModel().
189                                                       getLeadSelectionIndex();
190      if (rowLead >= 0 && rowLead < rowCount && columnLead >= 0
191          && columnLead < columnCount)
192        {
193          Rectangle dirtyRect = table.getCellRect(rowLead, columnLead, false);
194          table.repaint(dirtyRect);
195        }
196    }
197  }
198
199  public class MouseInputHandler implements MouseInputListener
200  {
201    Point begin, curr;
202
203    private void updateSelection(boolean controlPressed)
204    {
205      // Update the rows
206      int lo_row = table.rowAtPoint(begin);
207      int hi_row  = table.rowAtPoint(curr);
208      ListSelectionModel rowModel = table.getSelectionModel();
209      if (lo_row != -1 && hi_row != -1)
210        {
211          if (controlPressed && rowModel.getSelectionMode() 
212              != ListSelectionModel.SINGLE_SELECTION)
213            rowModel.addSelectionInterval(lo_row, hi_row);
214          else
215            rowModel.setSelectionInterval(lo_row, hi_row);
216        }
217      
218      // Update the columns
219      int lo_col = table.columnAtPoint(begin);
220      int hi_col = table.columnAtPoint(curr);
221      ListSelectionModel colModel = table.getColumnModel().
222        getSelectionModel();
223      if (lo_col != -1 && hi_col != -1)
224        {
225          if (controlPressed && colModel.getSelectionMode() != 
226              ListSelectionModel.SINGLE_SELECTION)
227            colModel.addSelectionInterval(lo_col, hi_col);
228          else
229            colModel.setSelectionInterval(lo_col, hi_col);
230        }
231    }
232    
233    /**
234     * For the double click, start the cell editor.
235     */
236    public void mouseClicked(MouseEvent e)
237    {
238      Point p = e.getPoint();
239      int row = table.rowAtPoint(p);
240      int col = table.columnAtPoint(p);
241      if (table.isCellEditable(row, col))
242        {
243          // If the cell editor is the default editor, we request the
244          // number of the required clicks from it. Otherwise,
245          // require two clicks (double click).
246          TableCellEditor editor = table.getCellEditor(row, col);
247          if (editor instanceof DefaultCellEditor)
248            {
249              DefaultCellEditor ce = (DefaultCellEditor) editor;
250              if (e.getClickCount() < ce.getClickCountToStart())
251                return;
252            }
253          table.editCellAt(row, col);
254        }
255    }
256
257    public void mouseDragged(MouseEvent e) 
258    {
259      if (table.isEnabled())
260        {
261          curr = new Point(e.getX(), e.getY());
262          updateSelection(e.isControlDown());
263        }
264    }
265
266    public void mouseEntered(MouseEvent e)
267    {
268      // Nothing to do here.
269    }
270
271    public void mouseExited(MouseEvent e)
272    {
273      // Nothing to do here.
274    }
275
276    public void mouseMoved(MouseEvent e)
277    {
278      // Nothing to do here.
279    }
280
281    public void mousePressed(MouseEvent e) 
282    {
283      if (table.isEnabled())
284        {
285          ListSelectionModel rowModel = table.getSelectionModel();
286          ListSelectionModel colModel = table.getColumnModel().getSelectionModel();
287          int rowLead = rowModel.getLeadSelectionIndex();
288          int colLead = colModel.getLeadSelectionIndex();
289
290          begin = new Point(e.getX(), e.getY());
291          curr = new Point(e.getX(), e.getY());
292          //if control is pressed and the cell is already selected, deselect it
293          if (e.isControlDown() && table.isCellSelected(
294              table.rowAtPoint(begin), table.columnAtPoint(begin)))
295            {                                       
296              table.getSelectionModel().
297              removeSelectionInterval(table.rowAtPoint(begin), 
298                                      table.rowAtPoint(begin));
299              table.getColumnModel().getSelectionModel().
300              removeSelectionInterval(table.columnAtPoint(begin), 
301                                      table.columnAtPoint(begin));
302            }
303          else
304            updateSelection(e.isControlDown());
305
306          // If we were editing, but the moved to another cell, stop editing
307          if (rowLead != rowModel.getLeadSelectionIndex() ||
308              colLead != colModel.getLeadSelectionIndex())
309            if (table.isEditing())
310              table.editingStopped(new ChangeEvent(e));
311
312          // Must request focus explicitly.
313          table.requestFocusInWindow();
314        }
315    }
316
317    public void mouseReleased(MouseEvent e) 
318    {
319      if (table.isEnabled())
320        {
321          begin = null;
322          curr = null;
323        }
324    }
325  }
326
327  /**
328   * Listens for changes to the model property of the JTable and adjusts some
329   * settings.
330   *
331   * @author Roman Kennke (kennke@aicas.com)
332   */
333  private class PropertyChangeHandler implements PropertyChangeListener
334  {
335    /**
336     * Receives notification if one of the JTable's properties changes.
337     *
338     * @param ev the property change event
339     */
340    public void propertyChange(PropertyChangeEvent ev)
341    {
342      String propName = ev.getPropertyName();
343      if (propName.equals("model"))
344        {
345          ListSelectionModel rowSel = table.getSelectionModel();
346          rowSel.clearSelection();
347          ListSelectionModel colSel = table.getColumnModel().getSelectionModel();
348          colSel.clearSelection();
349          TableModel model = table.getModel();
350
351          // Adjust lead and anchor selection indices of the row and column
352          // selection models.
353          if (model.getRowCount() > 0)
354            {
355              rowSel.setAnchorSelectionIndex(0);
356              rowSel.setLeadSelectionIndex(0);
357            }
358          else
359            {
360              rowSel.setAnchorSelectionIndex(-1);
361              rowSel.setLeadSelectionIndex(-1);
362            }
363          if (model.getColumnCount() > 0)
364            {
365              colSel.setAnchorSelectionIndex(0);
366              colSel.setLeadSelectionIndex(0);
367            }
368          else
369            {
370              colSel.setAnchorSelectionIndex(-1);
371              colSel.setLeadSelectionIndex(-1);
372            }
373        }
374    }
375  }
376
377  protected FocusListener createFocusListener() 
378  {
379    return new FocusHandler();
380  }
381
382  protected MouseInputListener createMouseInputListener() 
383  {
384    return new MouseInputHandler();
385  }
386
387
388  /**
389   * Creates and returns a key listener for the JTable.
390   *
391   * @return a key listener for the JTable
392   */
393  protected KeyListener createKeyListener()
394  {
395    return new KeyHandler();
396  }
397
398  /**
399   * Return the maximum size of the table. The maximum height is the row 
400    * height times the number of rows. The maximum width is the sum of 
401    * the maximum widths of each column.
402    * 
403    *  @param comp the component whose maximum size is being queried,
404    *  this is ignored.
405    *  @return a Dimension object representing the maximum size of the table,
406    *  or null if the table has no elements.
407   */
408  public Dimension getMaximumSize(JComponent comp) 
409  {
410    int maxTotalColumnWidth = 0;
411    for (int i = 0; i < table.getColumnCount(); i++)
412      maxTotalColumnWidth += table.getColumnModel().getColumn(i).getMaxWidth();
413
414    return new Dimension(maxTotalColumnWidth, getHeight());
415  }
416
417  /**
418   * Return the minimum size of the table. The minimum height is the row 
419    * height times the number of rows. The minimum width is the sum of 
420    * the minimum widths of each column.
421    * 
422    *  @param comp the component whose minimum size is being queried,
423    *  this is ignored.
424    *  @return a Dimension object representing the minimum size of the table,
425    *  or null if the table has no elements.
426   */
427  public Dimension getMinimumSize(JComponent comp) 
428  {
429    int minTotalColumnWidth = 0;
430    for (int i = 0; i < table.getColumnCount(); i++)
431      minTotalColumnWidth += table.getColumnModel().getColumn(i).getMinWidth();
432
433    return new Dimension(minTotalColumnWidth, getHeight());
434  }
435
436  /**
437   * Returns the preferred size for the table of that UI.
438   *
439   * @param comp ignored, the <code>table</code> field is used instead
440   *
441   * @return the preferred size for the table of that UI
442   */
443  public Dimension getPreferredSize(JComponent comp) 
444  {
445    int prefTotalColumnWidth = 0;
446    TableColumnModel tcm = table.getColumnModel();
447
448    for (int i = 0; i < tcm.getColumnCount(); i++)
449      {
450        TableColumn col = tcm.getColumn(i);
451        prefTotalColumnWidth += col.getPreferredWidth();
452      }
453
454    return new Dimension(prefTotalColumnWidth, getHeight());
455  }
456
457  /**
458   * Returns the table height. This helper method is used by
459   * {@link #getMinimumSize(JComponent)}, {@link #getPreferredSize(JComponent)}
460   * and {@link #getMaximumSize(JComponent)} to determine the table height.
461   * 
462   * @return the table height
463   */
464  private int getHeight()
465  {
466    int height = 0;
467    int rowCount = table.getRowCount(); 
468    if (rowCount > 0 && table.getColumnCount() > 0)
469      {
470        Rectangle r = table.getCellRect(rowCount - 1, 0, true);
471        height = r.y + r.height;
472      }
473    return height;
474  }
475
476  protected void installDefaults() 
477  {
478    LookAndFeel.installColorsAndFont(table, "Table.background",
479                                     "Table.foreground", "Table.font");
480    table.setGridColor(UIManager.getColor("Table.gridColor"));
481    table.setSelectionForeground(UIManager.getColor("Table.selectionForeground"));
482    table.setSelectionBackground(UIManager.getColor("Table.selectionBackground"));
483    table.setOpaque(true);
484  }
485
486  /**
487   * Installs keyboard actions on the table.
488   */
489  protected void installKeyboardActions() 
490  {
491    // Install the input map.
492    InputMap inputMap =
493      (InputMap) SharedUIDefaults.get("Table.ancestorInputMap");
494    SwingUtilities.replaceUIInputMap(table,
495                                 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
496                                 inputMap);
497
498    // FIXME: The JDK uses a LazyActionMap for parentActionMap
499    SwingUtilities.replaceUIActionMap(table, getActionMap());
500
501  }
502
503  /**
504   * Fetches the action map from  the UI defaults, or create a new one
505   * if the action map hasn't been initialized.
506   *
507   * @return the action map
508   */
509  private ActionMap getActionMap()
510  {
511    ActionMap am = (ActionMap) UIManager.get("Table.actionMap");
512    if (am == null)
513      {
514        am = createDefaultActions();
515        UIManager.getLookAndFeelDefaults().put("Table.actionMap", am);
516      }
517    return am;
518  }
519
520  private ActionMap createDefaultActions()
521  {
522    ActionMapUIResource am = new ActionMapUIResource();
523    Action action = new TableAction();
524
525    am.put("cut", TransferHandler.getCutAction());
526    am.put("copy", TransferHandler.getCopyAction());
527    am.put("paste", TransferHandler.getPasteAction());
528
529    am.put("cancel", action);
530    am.put("selectAll", action);
531    am.put("clearSelection", action);
532    am.put("startEditing", action);
533
534    am.put("selectNextRow", action);
535    am.put("selectNextRowCell", action);
536    am.put("selectNextRowExtendSelection", action);
537    am.put("selectNextRowChangeLead", action);
538
539    am.put("selectPreviousRow", action);
540    am.put("selectPreviousRowCell", action);
541    am.put("selectPreviousRowExtendSelection", action);
542    am.put("selectPreviousRowChangeLead", action);
543
544    am.put("selectNextColumn", action);
545    am.put("selectNextColumnCell", action);
546    am.put("selectNextColumnExtendSelection", action);
547    am.put("selectNextColumnChangeLead", action);
548
549    am.put("selectPreviousColumn", action);
550    am.put("selectPreviousColumnCell", action);
551    am.put("selectPreviousColumnExtendSelection", action);
552    am.put("selectPreviousColumnChangeLead", action);
553
554    am.put("scrollLeftChangeSelection", action);
555    am.put("scrollLeftExtendSelection", action);
556    am.put("scrollRightChangeSelection", action);
557    am.put("scrollRightExtendSelection", action);
558
559    am.put("scrollUpChangeSelection", action);
560    am.put("scrollUpExtendSelection", action);
561    am.put("scrollDownChangeSelection", action);
562    am.put("scrolldownExtendSelection", action);
563
564    am.put("selectFirstColumn", action);
565    am.put("selectFirstColumnExtendSelection", action);
566    am.put("selectLastColumn", action);
567    am.put("selectLastColumnExtendSelection", action);
568
569    am.put("selectFirstRow", action);
570    am.put("selectFirstRowExtendSelection", action);
571    am.put("selectLastRow", action);
572    am.put("selectLastRowExtendSelection", action);
573
574    am.put("addToSelection", action);
575    am.put("toggleAndAnchor", action);
576    am.put("extendTo", action);
577    am.put("moveSelectionTo", action);
578
579    return am;
580  }
581
582  /**
583   * This class implements the actions that we want to happen
584   * when specific keys are pressed for the JTable.  The actionPerformed
585   * method is called when a key that has been registered for the JTable
586   * is received.
587   */
588  private static class TableAction
589    extends AbstractAction
590  {
591    /**
592     * What to do when this action is called.
593     *
594     * @param e the ActionEvent that caused this action.
595     */
596    public void actionPerformed(ActionEvent e)
597    {
598      JTable table = (JTable) e.getSource();
599
600      DefaultListSelectionModel rowModel 
601          = (DefaultListSelectionModel) table.getSelectionModel();
602      DefaultListSelectionModel colModel 
603          = (DefaultListSelectionModel) table.getColumnModel().getSelectionModel();
604
605      int rowLead = rowModel.getLeadSelectionIndex();
606      int rowMax = table.getModel().getRowCount() - 1;
607      
608      int colLead = colModel.getLeadSelectionIndex();
609      int colMax = table.getModel().getColumnCount() - 1;
610
611      // The command with which the action has been called is stored
612      // in this undocumented action value. This allows us to have only
613      // one Action instance to serve all keyboard input for JTable.
614      String command = (String) getValue("__command__");
615      if (command.equals("selectPreviousRowExtendSelection"))
616        {
617          rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0));
618        }
619      else if (command.equals("selectLastColumn"))
620        {
621          colModel.setSelectionInterval(colMax, colMax);
622        }
623      else if (command.equals("startEditing"))
624        {
625          if (table.isCellEditable(rowLead, colLead))
626            table.editCellAt(rowLead, colLead);
627        }
628      else if (command.equals("selectFirstRowExtendSelection"))
629        {              
630          rowModel.setLeadSelectionIndex(0);
631        }
632      else if (command.equals("selectFirstColumn"))
633        {
634          colModel.setSelectionInterval(0, 0);
635        }
636      else if (command.equals("selectFirstColumnExtendSelection"))
637        {
638          colModel.setLeadSelectionIndex(0);
639        }      
640      else if (command.equals("selectLastRow"))
641        {
642          rowModel.setSelectionInterval(rowMax, rowMax);
643        }
644      else if (command.equals("selectNextRowExtendSelection"))
645        {
646          rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
647        }
648      else if (command.equals("selectFirstRow"))
649        {
650          rowModel.setSelectionInterval(0, 0);
651        }
652      else if (command.equals("selectNextColumnExtendSelection"))
653        {
654          colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax));
655        }
656      else if (command.equals("selectLastColumnExtendSelection"))
657        {
658          colModel.setLeadSelectionIndex(colMax);
659        }
660      else if (command.equals("selectPreviousColumnExtendSelection"))
661        {
662          colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0));
663        }
664      else if (command.equals("selectNextRow"))
665        {
666          rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
667                                        Math.min(rowLead + 1, rowMax));
668        }
669      else if (command.equals("scrollUpExtendSelection"))
670        {
671          int target;
672          if (rowLead == getFirstVisibleRowIndex(table))
673            target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) 
674                - getFirstVisibleRowIndex(table) + 1));
675          else
676            target = getFirstVisibleRowIndex(table);
677          
678          rowModel.setLeadSelectionIndex(target);
679          colModel.setLeadSelectionIndex(colLead);
680        }
681      else if (command.equals("selectPreviousRow"))
682        {
683          rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
684                                        Math.max(rowLead - 1, 0));
685        }
686      else if (command.equals("scrollRightChangeSelection"))
687        {
688          int target;
689          if (colLead == getLastVisibleColumnIndex(table))
690            target = Math.min(colMax, colLead
691                              + (getLastVisibleColumnIndex(table)
692                              - getFirstVisibleColumnIndex(table) + 1));
693          else
694            target = getLastVisibleColumnIndex(table);
695          
696          colModel.setSelectionInterval(target, target);
697          rowModel.setSelectionInterval(rowLead, rowLead);
698        }
699      else if (command.equals("selectPreviousColumn"))
700        {
701          colModel.setSelectionInterval(Math.max(colLead - 1, 0),
702                                        Math.max(colLead - 1, 0));
703        }
704      else if (command.equals("scrollLeftChangeSelection"))
705        {
706          int target;
707          if (colLead == getFirstVisibleColumnIndex(table))
708            target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) 
709                                 - getFirstVisibleColumnIndex(table) + 1));
710          else
711            target = getFirstVisibleColumnIndex(table);
712          
713          colModel.setSelectionInterval(target, target);
714          rowModel.setSelectionInterval(rowLead, rowLead);
715        }
716      else if (command.equals("clearSelection"))
717        {
718          table.clearSelection();
719        }
720      else if (command.equals("cancel"))
721        {
722          // FIXME: implement other parts of "cancel" like undo-ing last
723          // selection.  Right now it just calls editingCancelled if
724          // we're currently editing.
725          if (table.isEditing())
726            table.editingCanceled(new ChangeEvent("cancel"));
727        }
728      else if (command.equals("selectNextRowCell")
729               || command.equals("selectPreviousRowCell")
730               || command.equals("selectNextColumnCell")
731               || command.equals("selectPreviousColumnCell"))
732        {
733          // If nothing is selected, select the first cell in the table
734          if (table.getSelectedRowCount() == 0 && 
735              table.getSelectedColumnCount() == 0)
736            {
737              rowModel.setSelectionInterval(0, 0);
738              colModel.setSelectionInterval(0, 0);
739              return;
740            }
741          
742          // If the lead selection index isn't selected (ie a remove operation
743          // happened, then set the lead to the first selected cell in the
744          // table
745          if (!table.isCellSelected(rowLead, colLead))
746            {
747              rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(), 
748                                            rowModel.getMinSelectionIndex());
749              colModel.addSelectionInterval(colModel.getMinSelectionIndex(), 
750                                            colModel.getMinSelectionIndex());
751              return;
752            }
753          
754          // multRowsSelected and multColsSelected tell us if multiple rows or
755          // columns are selected, respectively
756          boolean multRowsSelected, multColsSelected;
757          multRowsSelected = table.getSelectedRowCount() > 1 &&
758            table.getRowSelectionAllowed();
759          
760          multColsSelected = table.getSelectedColumnCount() > 1 &&
761            table.getColumnSelectionAllowed();
762          
763          // If there is just one selection, select the next cell, and wrap
764          // when you get to the edges of the table.
765          if (!multColsSelected && !multRowsSelected)
766            {
767              if (command.indexOf("Column") != -1) 
768                advanceSingleSelection(colModel, colMax, rowModel, rowMax, 
769                    command.equals("selectPreviousColumnCell"));
770              else
771                advanceSingleSelection(rowModel, rowMax, colModel, colMax, 
772                    command.equals("selectPreviousRowCell"));
773              return;
774            }
775          
776          
777          // rowMinSelected and rowMaxSelected are the minimum and maximum
778          // values respectively of selected cells in the row selection model
779          // Similarly for colMinSelected and colMaxSelected.
780          int rowMaxSelected = table.getRowSelectionAllowed() ? 
781            rowModel.getMaxSelectionIndex() : table.getModel().getRowCount() - 1;
782          int rowMinSelected = table.getRowSelectionAllowed() ? 
783            rowModel.getMinSelectionIndex() : 0; 
784          int colMaxSelected = table.getColumnSelectionAllowed() ? 
785            colModel.getMaxSelectionIndex() : 
786            table.getModel().getColumnCount() - 1;
787          int colMinSelected = table.getColumnSelectionAllowed() ? 
788            colModel.getMinSelectionIndex() : 0;
789          
790          // If there are multiple rows and columns selected, select the next
791          // cell and wrap at the edges of the selection.  
792          if (command.indexOf("Column") != -1) 
793            advanceMultipleSelection(table, colModel, colMinSelected,
794                                     colMaxSelected, rowModel, rowMinSelected,
795                                     rowMaxSelected,
796                                    command.equals("selectPreviousColumnCell"),
797                                    true);
798          
799          else
800            advanceMultipleSelection(table, rowModel, rowMinSelected,
801                                     rowMaxSelected, colModel, colMinSelected,
802                                     colMaxSelected,
803                                     command.equals("selectPreviousRowCell"),
804                                     false);
805        }
806      else if (command.equals("selectNextColumn"))
807        {
808          colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
809                                        Math.min(colLead + 1, colMax));
810        }
811      else if (command.equals("scrollLeftExtendSelection"))
812        {
813          int target;
814          if (colLead == getFirstVisibleColumnIndex(table))
815            target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) 
816                                 - getFirstVisibleColumnIndex(table) + 1));
817          else
818            target = getFirstVisibleColumnIndex(table);
819          
820          colModel.setLeadSelectionIndex(target);
821          rowModel.setLeadSelectionIndex(rowLead);
822        }
823      else if (command.equals("scrollDownChangeSelection"))
824        {
825          int target;
826          if (rowLead == getLastVisibleRowIndex(table))
827            target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table)
828                                      - getFirstVisibleRowIndex(table) + 1));
829          else
830            target = getLastVisibleRowIndex(table);
831          
832          rowModel.setSelectionInterval(target, target);
833          colModel.setSelectionInterval(colLead, colLead);
834        }
835      else if (command.equals("scrollRightExtendSelection"))
836        {
837          int target;
838          if (colLead == getLastVisibleColumnIndex(table))
839            target = Math.min(colMax, colLead + (getLastVisibleColumnIndex(table) 
840                - getFirstVisibleColumnIndex(table) + 1));
841          else
842            target = getLastVisibleColumnIndex(table);
843          
844          colModel.setLeadSelectionIndex(target);
845          rowModel.setLeadSelectionIndex(rowLead);
846        }
847      else if (command.equals("selectAll"))
848        {
849          table.selectAll();
850        }
851      else if (command.equals("selectLastRowExtendSelection"))
852        {
853          rowModel.setLeadSelectionIndex(rowMax);
854          colModel.setLeadSelectionIndex(colLead);
855        }
856      else if (command.equals("scrollDownExtendSelection"))
857        {
858          int target;
859          if (rowLead == getLastVisibleRowIndex(table))
860            target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table) 
861                - getFirstVisibleRowIndex(table) + 1));
862          else
863            target = getLastVisibleRowIndex(table);
864          
865          rowModel.setLeadSelectionIndex(target);
866          colModel.setLeadSelectionIndex(colLead);
867        }      
868      else if (command.equals("scrollUpChangeSelection"))
869        {
870          int target;
871          if (rowLead == getFirstVisibleRowIndex(table))
872            target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) 
873                - getFirstVisibleRowIndex(table) + 1));
874          else
875            target = getFirstVisibleRowIndex(table);
876          
877          rowModel.setSelectionInterval(target, target);
878          colModel.setSelectionInterval(colLead, colLead);
879        }
880      else if (command.equals("selectNextRowChangeLead"))
881          {
882            if (rowModel.getSelectionMode() 
883                != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
884              {
885                // just "selectNextRow"
886                rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
887                                              Math.min(rowLead + 1, rowMax));
888                colModel.setSelectionInterval(colLead, colLead);
889              }
890            else
891              rowModel.moveLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
892          }
893      else if (command.equals("selectPreviousRowChangeLead"))
894        {
895          if (rowModel.getSelectionMode() 
896              != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
897            {
898              // just selectPreviousRow
899              rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
900                                            Math.min(rowLead - 1, 0));
901              colModel.setSelectionInterval(colLead, colLead);
902            }
903          else
904            rowModel.moveLeadSelectionIndex(Math.max(rowLead - 1, 0));
905        }
906      else if (command.equals("selectNextColumnChangeLead"))
907        {
908          if (colModel.getSelectionMode() 
909              != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)            
910            {
911              // just selectNextColumn
912              rowModel.setSelectionInterval(rowLead, rowLead);
913              colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
914                                            Math.min(colLead + 1, colMax));
915            }
916          else
917            colModel.moveLeadSelectionIndex(Math.min(colLead + 1, colMax));
918        }
919      else if (command.equals("selectPreviousColumnChangeLead"))
920        {
921          if (colModel.getSelectionMode() 
922              != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)            
923            {
924              // just selectPreviousColumn
925              rowModel.setSelectionInterval(rowLead, rowLead);
926              colModel.setSelectionInterval(Math.max(colLead - 1, 0),
927                                            Math.max(colLead - 1, 0));
928              
929            }
930          else
931            colModel.moveLeadSelectionIndex(Math.max(colLead - 1, 0));
932        }
933      else if (command.equals("addToSelection"))
934          {
935            if (!table.isEditing())
936              {
937                int oldRowAnchor = rowModel.getAnchorSelectionIndex();
938                int oldColAnchor = colModel.getAnchorSelectionIndex();
939                rowModel.addSelectionInterval(rowLead, rowLead);
940                colModel.addSelectionInterval(colLead, colLead);
941                rowModel.setAnchorSelectionIndex(oldRowAnchor);
942                colModel.setAnchorSelectionIndex(oldColAnchor);
943              }
944          }
945      else if (command.equals("extendTo"))
946        {
947          rowModel.setSelectionInterval(rowModel.getAnchorSelectionIndex(),
948                                        rowLead);
949          colModel.setSelectionInterval(colModel.getAnchorSelectionIndex(),
950                                        colLead);
951        }
952      else if (command.equals("toggleAndAnchor"))
953        {
954          if (rowModel.isSelectedIndex(rowLead))
955            rowModel.removeSelectionInterval(rowLead, rowLead);
956          else
957            rowModel.addSelectionInterval(rowLead, rowLead);
958          
959          if (colModel.isSelectedIndex(colLead))
960            colModel.removeSelectionInterval(colLead, colLead);
961          else
962            colModel.addSelectionInterval(colLead, colLead);
963          
964          rowModel.setAnchorSelectionIndex(rowLead);
965          colModel.setAnchorSelectionIndex(colLead);
966        }
967      else if (command.equals("stopEditing"))
968        {
969          table.editingStopped(new ChangeEvent(command));
970        }
971      else 
972        {
973          // If we're here that means we bound this TableAction class
974          // to a keyboard input but we either want to ignore that input
975          // or we just haven't implemented its action yet.
976          
977          // Uncomment the following line to print the names of unused bindings
978          // when their keys are pressed
979          
980          // System.out.println ("not implemented: "+e.getActionCommand());
981        }
982
983      // Any commands whose keyStrokes should be used by the Editor should not
984      // cause editing to be stopped: ie, the SPACE sends "addToSelection" but 
985      // if the table is in editing mode, the space should not cause us to stop
986      // editing because it should be used by the Editor.
987      if (table.isEditing() && command != "startEditing"
988          && command != "addToSelection")
989        table.editingStopped(new ChangeEvent("update"));
990            
991      table.scrollRectToVisible(table.getCellRect(
992          rowModel.getLeadSelectionIndex(), colModel.getLeadSelectionIndex(), 
993          false));
994    }
995    
996    /**
997     * Returns the column index of the first visible column.
998     * @return the column index of the first visible column.
999     */
1000    int getFirstVisibleColumnIndex(JTable table)
1001    {
1002      ComponentOrientation or = table.getComponentOrientation();
1003      Rectangle r = table.getVisibleRect();
1004      if (!or.isLeftToRight())
1005        r.translate((int) r.getWidth() - 1, 0);
1006      return table.columnAtPoint(r.getLocation());
1007    }
1008    
1009    /**
1010     * Returns the column index of the last visible column.
1011     *
1012     */
1013    int getLastVisibleColumnIndex(JTable table)
1014    {
1015      ComponentOrientation or = table.getComponentOrientation();
1016      Rectangle r = table.getVisibleRect();
1017      if (or.isLeftToRight())
1018        r.translate((int) r.getWidth() - 1, 0);
1019      return table.columnAtPoint(r.getLocation());      
1020    }
1021    
1022    /**
1023     * Returns the row index of the first visible row.
1024     *
1025     */
1026    int getFirstVisibleRowIndex(JTable table)
1027    {
1028      ComponentOrientation or = table.getComponentOrientation();
1029      Rectangle r = table.getVisibleRect();
1030      if (!or.isLeftToRight())
1031        r.translate((int) r.getWidth() - 1, 0);
1032      return table.rowAtPoint(r.getLocation());
1033    }
1034    
1035    /**
1036     * Returns the row index of the last visible row.
1037     *
1038     */
1039    int getLastVisibleRowIndex(JTable table)
1040    {
1041      ComponentOrientation or = table.getComponentOrientation();
1042      Rectangle r = table.getVisibleRect();
1043      r.translate(0, (int) r.getHeight() - 1);
1044      if (or.isLeftToRight())
1045        r.translate((int) r.getWidth() - 1, 0);
1046      // The next if makes sure that we don't return -1 simply because
1047      // there is white space at the bottom of the table (ie, the display
1048      // area is larger than the table)
1049      if (table.rowAtPoint(r.getLocation()) == -1)
1050        {
1051          if (getFirstVisibleRowIndex(table) == -1)
1052            return -1;
1053          else
1054            return table.getModel().getRowCount() - 1;
1055        }
1056      return table.rowAtPoint(r.getLocation());
1057    }
1058
1059    /**
1060     * A helper method for the key bindings.  Used because the actions
1061     * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
1062     *
1063     * Selects the next (previous if SHIFT pressed) column for TAB, or row for
1064     * ENTER from within the currently selected cells.
1065     *
1066     * @param firstModel the ListSelectionModel for columns (TAB) or
1067     * rows (ENTER)
1068     * @param firstMin the first selected index in firstModel
1069     * @param firstMax the last selected index in firstModel
1070     * @param secondModel the ListSelectionModel for rows (TAB) or 
1071     * columns (ENTER)
1072     * @param secondMin the first selected index in secondModel
1073     * @param secondMax the last selected index in secondModel
1074     * @param reverse true if shift was held for the event
1075     * @param eventIsTab true if TAB was pressed, false if ENTER pressed
1076     */
1077    void advanceMultipleSelection(JTable table, ListSelectionModel firstModel,
1078                                  int firstMin,
1079                                  int firstMax, ListSelectionModel secondModel, 
1080                                  int secondMin, int secondMax, boolean reverse,
1081                                  boolean eventIsTab)
1082    {
1083      // If eventIsTab, all the "firsts" correspond to columns, otherwise, to 
1084      // rows "seconds" correspond to the opposite
1085      int firstLead = firstModel.getLeadSelectionIndex();
1086      int secondLead = secondModel.getLeadSelectionIndex();
1087      int numFirsts = eventIsTab ? 
1088        table.getModel().getColumnCount() : table.getModel().getRowCount();
1089      int numSeconds = eventIsTab ? 
1090        table.getModel().getRowCount() : table.getModel().getColumnCount();
1091
1092      // check if we have to wrap the "firsts" around, going to the other side
1093      if ((firstLead == firstMax && !reverse) || 
1094          (reverse && firstLead == firstMin))
1095        {
1096          firstModel.addSelectionInterval(reverse ? firstMax : firstMin, 
1097                                          reverse ? firstMax : firstMin);
1098          
1099          // check if we have to wrap the "seconds"
1100          if ((secondLead == secondMax && !reverse) || 
1101              (reverse && secondLead == secondMin))
1102            secondModel.addSelectionInterval(reverse ? secondMax : secondMin, 
1103                                             reverse ? secondMax : secondMin);
1104
1105          // if we're not wrapping the seconds, we have to find out where we
1106          // are within the secondModel and advance to the next cell (or 
1107          // go back to the previous cell if reverse == true)
1108          else
1109            {
1110              int[] secondsSelected;
1111              if (eventIsTab && table.getRowSelectionAllowed() || 
1112                  !eventIsTab && table.getColumnSelectionAllowed())
1113                secondsSelected = eventIsTab ? 
1114                  table.getSelectedRows() : table.getSelectedColumns();
1115              else
1116                {
1117                  // if row selection is not allowed, then the entire column gets
1118                  // selected when you click on it, so consider ALL rows selected
1119                  secondsSelected = new int[numSeconds];
1120                  for (int i = 0; i < numSeconds; i++)
1121                  secondsSelected[i] = i;
1122                }
1123
1124              // and now find the "next" index within the model
1125              int secondIndex = reverse ? secondsSelected.length - 1 : 0;
1126              if (!reverse)
1127                while (secondsSelected[secondIndex] <= secondLead)
1128                  secondIndex++;
1129              else
1130                while (secondsSelected[secondIndex] >= secondLead)
1131                  secondIndex--;
1132              
1133              // and select it - updating the lead selection index
1134              secondModel.addSelectionInterval(secondsSelected[secondIndex], 
1135                                               secondsSelected[secondIndex]);
1136            }
1137        }
1138      // We didn't have to wrap the firsts, so just find the "next" first
1139      // and select it, we don't have to change "seconds"
1140      else
1141        {
1142          int[] firstsSelected;
1143          if (eventIsTab && table.getColumnSelectionAllowed() || 
1144              !eventIsTab && table.getRowSelectionAllowed())
1145            firstsSelected = eventIsTab ? 
1146              table.getSelectedColumns() : table.getSelectedRows();
1147          else
1148            {
1149              // if selection not allowed, consider ALL firsts to be selected
1150              firstsSelected = new int[numFirsts];
1151              for (int i = 0; i < numFirsts; i++)
1152                firstsSelected[i] = i;
1153            }
1154          int firstIndex = reverse ? firstsSelected.length - 1 : 0;
1155          if (!reverse)
1156            while (firstsSelected[firstIndex] <= firstLead)
1157              firstIndex++;
1158          else 
1159            while (firstsSelected[firstIndex] >= firstLead)
1160              firstIndex--;
1161          firstModel.addSelectionInterval(firstsSelected[firstIndex], 
1162                                          firstsSelected[firstIndex]);
1163          secondModel.addSelectionInterval(secondLead, secondLead);
1164        }
1165    }
1166    
1167    /** 
1168     * A helper method for the key  bindings. Used because the actions
1169     * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
1170     *
1171     * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER)
1172     * in the table, changing the current selection.  All cells in the table
1173     * are eligible, not just the ones that are currently selected.
1174     * @param firstModel the ListSelectionModel for columns (TAB) or rows
1175     * (ENTER)
1176     * @param firstMax the last index in firstModel
1177     * @param secondModel the ListSelectionModel for rows (TAB) or columns
1178     * (ENTER)
1179     * @param secondMax the last index in secondModel
1180     * @param reverse true if SHIFT was pressed for the event
1181     */
1182
1183    void advanceSingleSelection(ListSelectionModel firstModel, int firstMax, 
1184                                ListSelectionModel secondModel, int secondMax, 
1185                                boolean reverse)
1186    {
1187      // for TABs, "first" corresponds to columns and "seconds" to rows.
1188      // the opposite is true for ENTERs
1189      int firstLead = firstModel.getLeadSelectionIndex();
1190      int secondLead = secondModel.getLeadSelectionIndex();
1191      
1192      // if we are going backwards subtract 2 because we later add 1
1193      // for a net change of -1
1194      if (reverse && (firstLead == 0))
1195        {
1196          // check if we have to wrap around
1197          if (secondLead == 0)
1198            secondLead += secondMax + 1;
1199          secondLead -= 2;
1200        }
1201      
1202      // do we have to wrap the "seconds"?
1203      if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax))
1204        secondModel.setSelectionInterval((secondLead + 1) % (secondMax + 1), 
1205                                         (secondLead + 1) % (secondMax + 1));
1206      // if not, just reselect the current lead
1207      else
1208        secondModel.setSelectionInterval(secondLead, secondLead);
1209      
1210      // if we are going backwards, subtract 2  because we add 1 later
1211      // for net change of -1
1212      if (reverse)
1213        {
1214          // check for wraparound
1215          if (firstLead == 0)
1216            firstLead += firstMax + 1;
1217          firstLead -= 2;
1218        }
1219      // select the next "first"
1220      firstModel.setSelectionInterval((firstLead + 1) % (firstMax + 1), 
1221                                      (firstLead + 1) % (firstMax + 1));
1222    }
1223  }
1224
1225  protected void installListeners() 
1226  {
1227    if (focusListener == null)
1228      focusListener = createFocusListener();
1229    table.addFocusListener(focusListener);
1230    if (keyListener == null)
1231      keyListener = createKeyListener();
1232    table.addKeyListener(keyListener);
1233    if (mouseInputListener == null)
1234      mouseInputListener = createMouseInputListener();
1235    table.addMouseListener(mouseInputListener);    
1236    table.addMouseMotionListener(mouseInputListener);
1237    if (propertyChangeListener == null)
1238      propertyChangeListener = new PropertyChangeHandler();
1239    table.addPropertyChangeListener(propertyChangeListener);
1240  }
1241
1242  /**
1243   * Uninstalls UI defaults that have been installed by
1244   * {@link #installDefaults()}.
1245   */
1246  protected void uninstallDefaults()
1247  {
1248    // Nothing to do here for now.
1249  }
1250
1251  /**
1252   * Uninstalls the keyboard actions that have been installed by
1253   * {@link #installKeyboardActions()}.
1254   */
1255  protected void uninstallKeyboardActions()
1256  {
1257    SwingUtilities.replaceUIInputMap(table, JComponent.
1258                                     WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1259    SwingUtilities.replaceUIActionMap(table, null);
1260  }
1261
1262  protected void uninstallListeners() 
1263  {
1264    table.removeFocusListener(focusListener);  
1265    table.removeKeyListener(keyListener);
1266    table.removeMouseListener(mouseInputListener);    
1267    table.removeMouseMotionListener(mouseInputListener);
1268    table.removePropertyChangeListener(propertyChangeListener);
1269    propertyChangeListener = null;
1270  }
1271
1272  public void installUI(JComponent comp) 
1273  {
1274    table = (JTable) comp;
1275    rendererPane = new CellRendererPane();
1276    table.add(rendererPane);
1277
1278    installDefaults();
1279    installKeyboardActions();
1280    installListeners();
1281  }
1282
1283  public void uninstallUI(JComponent c) 
1284  {
1285    uninstallListeners();
1286    uninstallKeyboardActions();
1287    uninstallDefaults(); 
1288
1289    table.remove(rendererPane);
1290    rendererPane = null;
1291    table = null;
1292  }
1293
1294  /**
1295   * Paints a single cell in the table.
1296   *
1297   * @param g The graphics context to paint in
1298   * @param row The row number to paint
1299   * @param col The column number to paint
1300   * @param bounds The bounds of the cell to paint, assuming a coordinate
1301   * system beginning at <code>(0,0)</code> in the upper left corner of the
1302   * table
1303   * @param rend A cell renderer to paint with
1304   */
1305  void paintCell(Graphics g, int row, int col, Rectangle bounds,
1306                 TableCellRenderer rend)
1307  {
1308    Component comp = table.prepareRenderer(rend, row, col);
1309    rendererPane.paintComponent(g, comp, table, bounds);
1310  }
1311  
1312  /**
1313   * Paint the associated table.
1314   */
1315  public void paint(Graphics gfx, JComponent ignored) 
1316  {
1317    int ncols = table.getColumnCount();
1318    int nrows = table.getRowCount();
1319    if (nrows == 0 || ncols == 0)
1320      return;
1321
1322    Rectangle clip = gfx.getClipBounds();
1323
1324    // Determine the range of cells that are within the clip bounds.
1325    Point p1 = new Point(clip.x, clip.y);
1326    int c0 = table.columnAtPoint(p1);
1327    if (c0 == -1)
1328      c0 = 0;
1329    int r0 = table.rowAtPoint(p1);
1330    if (r0 == -1)
1331      r0 = 0;
1332    Point p2 = new Point(clip.x + clip.width, clip.y + clip.height);
1333    int cn = table.columnAtPoint(p2);
1334    if (cn == -1)
1335      cn = table.getColumnCount() - 1;
1336    int rn = table.rowAtPoint(p2);
1337    if (rn == -1)
1338      rn = table.getRowCount() - 1;
1339
1340    int columnMargin = table.getColumnModel().getColumnMargin();
1341    int rowMargin = table.getRowMargin();
1342
1343    TableColumnModel cmodel = table.getColumnModel();
1344    int[] widths = new int[cn + 1];
1345    for (int i = c0; i <= cn; i++)
1346      {
1347        widths[i] = cmodel.getColumn(i).getWidth() - columnMargin;
1348      }
1349    
1350    Rectangle bounds = table.getCellRect(r0, c0, false);
1351    // The left boundary of the area being repainted.
1352    int left = bounds.x;
1353    
1354    // The top boundary of the area being repainted.
1355    int top = bounds.y;
1356    
1357    // The bottom boundary of the area being repainted.
1358    int bottom;
1359    
1360    // paint the cell contents
1361    Color grid = table.getGridColor();    
1362    for (int r = r0; r <= rn; ++r)
1363      {
1364        for (int c = c0; c <= cn; ++c)
1365          {
1366            bounds.width = widths[c];
1367            paintCell(gfx, r, c, bounds, table.getCellRenderer(r, c));
1368            bounds.x += widths[c] + columnMargin;
1369          }
1370        bounds.x = left;
1371        bounds.y += table.getRowHeight(r);
1372        // Update row height for tables with custom heights.
1373        bounds.height = table.getRowHeight(r + 1) - rowMargin;
1374      }
1375    
1376    bottom = bounds.y - rowMargin;
1377
1378    // paint vertical grid lines
1379    if (grid != null && table.getShowVerticalLines())
1380      {    
1381        Color save = gfx.getColor();
1382        gfx.setColor(grid);
1383        int x = left - columnMargin;
1384        for (int c = c0; c <= cn; ++c)
1385          {
1386            // The vertical grid is draw right from the cells, so we 
1387            // add before drawing.
1388            x += widths[c] + columnMargin;
1389            gfx.drawLine(x, top, x, bottom);
1390          }
1391        gfx.setColor(save);
1392      }
1393
1394    // paint horizontal grid lines    
1395    if (grid != null && table.getShowHorizontalLines())
1396      {    
1397        Color save = gfx.getColor();
1398        gfx.setColor(grid);
1399        int y = top - rowMargin;
1400        for (int r = r0; r <= rn; ++r)
1401          {
1402            // The horizontal grid is draw below the cells, so we 
1403            // add before drawing.
1404            y += table.getRowHeight(r);
1405            gfx.drawLine(left, y, p2.x, y);
1406          }
1407        gfx.setColor(save);
1408      }
1409  }
1410}