OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / plaf / basic / BasicTableUI.java
1 /* BasicTableUI.java --
2    Copyright (C) 2004 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.plaf.basic;
40
41 import java.awt.Color;
42 import java.awt.Component;
43 import java.awt.ComponentOrientation;
44 import java.awt.Dimension;
45 import java.awt.Graphics;
46 import java.awt.Point;
47 import java.awt.Rectangle;
48 import java.awt.event.ActionEvent;
49 import java.awt.event.FocusEvent;
50 import java.awt.event.FocusListener;
51 import java.awt.event.KeyEvent;
52 import java.awt.event.KeyListener;
53 import java.awt.event.MouseEvent;
54 import java.beans.PropertyChangeEvent;
55 import java.beans.PropertyChangeListener;
56
57 import javax.swing.AbstractAction;
58 import javax.swing.Action;
59 import javax.swing.ActionMap;
60 import javax.swing.CellRendererPane;
61 import javax.swing.DefaultCellEditor;
62 import javax.swing.DefaultListSelectionModel;
63 import javax.swing.InputMap;
64 import javax.swing.JComponent;
65 import javax.swing.JTable;
66 import javax.swing.ListSelectionModel;
67 import javax.swing.LookAndFeel;
68 import javax.swing.SwingUtilities;
69 import javax.swing.TransferHandler;
70 import javax.swing.UIManager;
71 import javax.swing.border.Border;
72 import javax.swing.event.ChangeEvent;
73 import javax.swing.event.MouseInputListener;
74 import javax.swing.plaf.ActionMapUIResource;
75 import javax.swing.plaf.ComponentUI;
76 import javax.swing.plaf.TableUI;
77 import javax.swing.table.TableCellEditor;
78 import javax.swing.table.TableCellRenderer;
79 import javax.swing.table.TableColumn;
80 import javax.swing.table.TableColumnModel;
81 import javax.swing.table.TableModel;
82
83 public class BasicTableUI extends TableUI
84 {
85   public static ComponentUI createUI(JComponent comp) 
86   {
87     return new BasicTableUI();
88   }
89
90   protected FocusListener focusListener;  
91   protected KeyListener keyListener;   
92   protected MouseInputListener  mouseInputListener;   
93   protected CellRendererPane rendererPane;   
94   protected JTable table;
95
96   /** The normal cell border. */
97   Border cellBorder;
98
99   /** 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 }