001    /* BasicTableUI.java --
002       Copyright (C) 2004 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.plaf.basic;
040    
041    import java.awt.Color;
042    import java.awt.Component;
043    import java.awt.ComponentOrientation;
044    import java.awt.Dimension;
045    import java.awt.Graphics;
046    import java.awt.Point;
047    import java.awt.Rectangle;
048    import java.awt.event.ActionEvent;
049    import java.awt.event.FocusEvent;
050    import java.awt.event.FocusListener;
051    import java.awt.event.KeyEvent;
052    import java.awt.event.KeyListener;
053    import java.awt.event.MouseEvent;
054    import java.beans.PropertyChangeEvent;
055    import java.beans.PropertyChangeListener;
056    
057    import javax.swing.AbstractAction;
058    import javax.swing.Action;
059    import javax.swing.ActionMap;
060    import javax.swing.CellRendererPane;
061    import javax.swing.DefaultCellEditor;
062    import javax.swing.DefaultListSelectionModel;
063    import javax.swing.InputMap;
064    import javax.swing.JComponent;
065    import javax.swing.JTable;
066    import javax.swing.ListSelectionModel;
067    import javax.swing.LookAndFeel;
068    import javax.swing.SwingUtilities;
069    import javax.swing.TransferHandler;
070    import javax.swing.UIManager;
071    import javax.swing.border.Border;
072    import javax.swing.event.ChangeEvent;
073    import javax.swing.event.MouseInputListener;
074    import javax.swing.plaf.ActionMapUIResource;
075    import javax.swing.plaf.ComponentUI;
076    import javax.swing.plaf.TableUI;
077    import javax.swing.table.TableCellEditor;
078    import javax.swing.table.TableCellRenderer;
079    import javax.swing.table.TableColumn;
080    import javax.swing.table.TableColumnModel;
081    import javax.swing.table.TableModel;
082    
083    public 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    }