Source for javax.swing.plaf.basic.BasicTableUI

   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 gnu.classpath.NotImplementedException;
  42: 
  43: import java.awt.Color;
  44: import java.awt.Component;
  45: import java.awt.ComponentOrientation;
  46: import java.awt.Dimension;
  47: import java.awt.Graphics;
  48: import java.awt.Point;
  49: import java.awt.Rectangle;
  50: import java.awt.event.ActionEvent;
  51: import java.awt.event.ActionListener;
  52: import java.awt.event.FocusEvent;
  53: import java.awt.event.FocusListener;
  54: import java.awt.event.KeyEvent;
  55: import java.awt.event.KeyListener;
  56: import java.awt.event.MouseEvent;
  57: import java.beans.PropertyChangeEvent;
  58: import java.beans.PropertyChangeListener;
  59: 
  60: import javax.swing.AbstractAction;
  61: import javax.swing.ActionMap;
  62: import javax.swing.CellRendererPane;
  63: import javax.swing.DefaultCellEditor;
  64: import javax.swing.DefaultListSelectionModel;
  65: import javax.swing.InputMap;
  66: import javax.swing.JComponent;
  67: import javax.swing.JTable;
  68: import javax.swing.KeyStroke;
  69: import javax.swing.ListSelectionModel;
  70: import javax.swing.LookAndFeel;
  71: import javax.swing.UIManager;
  72: import javax.swing.border.Border;
  73: import javax.swing.event.ChangeEvent;
  74: import javax.swing.event.MouseInputListener;
  75: import javax.swing.plaf.ActionMapUIResource;
  76: import javax.swing.plaf.ComponentUI;
  77: import javax.swing.plaf.InputMapUIResource;
  78: import javax.swing.plaf.TableUI;
  79: import javax.swing.table.TableCellEditor;
  80: import javax.swing.table.TableCellRenderer;
  81: import javax.swing.table.TableColumnModel;
  82: import javax.swing.table.TableModel;
  83: 
  84: public class BasicTableUI extends TableUI
  85: {
  86:   public static ComponentUI createUI(JComponent comp) 
  87:   {
  88:     return new BasicTableUI();
  89:   }
  90: 
  91:   protected FocusListener focusListener;  
  92:   protected KeyListener keyListener;   
  93:   protected MouseInputListener  mouseInputListener;   
  94:   protected CellRendererPane rendererPane;   
  95:   protected JTable table;
  96: 
  97:   /** The normal cell border. */
  98:   Border cellBorder;
  99: 
 100:   /** The action bound to KeyStrokes. */
 101:   TableAction action;
 102: 
 103:   /**
 104:    * Listens for changes to the tables properties.
 105:    */
 106:   private PropertyChangeListener propertyChangeListener;
 107: 
 108:   /**
 109:    * Handles key events for the JTable. Key events should be handled through
 110:    * the InputMap/ActionMap mechanism since JDK1.3. This class is only there
 111:    * for backwards compatibility.
 112:    * 
 113:    * @author Roman Kennke (kennke@aicas.com)
 114:    */
 115:   public class KeyHandler implements KeyListener
 116:   {
 117: 
 118:     /**
 119:      * Receives notification that a key has been pressed and released.
 120:      * Activates the editing session for the focused cell by pressing the
 121:      * character keys.
 122:      *
 123:      * @param event the key event
 124:      */
 125:     public void keyTyped(KeyEvent event)
 126:     {
 127:       // Key events should be handled through the InputMap/ActionMap mechanism
 128:       // since JDK1.3. This class is only there for backwards compatibility.
 129:       
 130:       // Editor activation is a specific kind of response to ''any''
 131:       // character key. Hence it is handled here.
 132:       if (!table.isEditing() && table.isEnabled())
 133:         {
 134:           int r = table.getSelectedRow();
 135:           int c = table.getSelectedColumn();
 136:           if (table.isCellEditable(r, c))
 137:             table.editCellAt(r, c);
 138:         }
 139:     }
 140: 
 141:     /**
 142:      * Receives notification that a key has been pressed.
 143:      *
 144:      * @param event the key event
 145:      */
 146:     public void keyPressed(KeyEvent event)
 147:     {
 148:       // Key events should be handled through the InputMap/ActionMap mechanism
 149:       // since JDK1.3. This class is only there for backwards compatibility.
 150:     }
 151: 
 152:     /**
 153:      * Receives notification that a key has been released.
 154:      *
 155:      * @param event the key event
 156:      */
 157:     public void keyReleased(KeyEvent event)
 158:     {
 159:       // Key events should be handled through the InputMap/ActionMap mechanism
 160:       // since JDK1.3. This class is only there for backwards compatibility.
 161:     }
 162:   }
 163: 
 164:   public class FocusHandler implements FocusListener
 165:   {
 166:     public void focusGained(FocusEvent e) 
 167:     {
 168:       // TODO: Implement this properly.
 169:     }
 170: 
 171:     public void focusLost(FocusEvent e) 
 172:     {
 173:       // TODO: Implement this properly.
 174:     }
 175:   }
 176: 
 177:   public class MouseInputHandler implements MouseInputListener
 178:   {
 179:     Point begin, curr;
 180: 
 181:     private void updateSelection(boolean controlPressed)
 182:     {
 183:       // Update the rows
 184:       int lo_row = table.rowAtPoint(begin);
 185:       int hi_row  = table.rowAtPoint(curr);
 186:       ListSelectionModel rowModel = table.getSelectionModel();
 187:       if (lo_row != -1 && hi_row != -1)
 188:         {
 189:           if (controlPressed && rowModel.getSelectionMode() 
 190:               != ListSelectionModel.SINGLE_SELECTION)
 191:             rowModel.addSelectionInterval(lo_row, hi_row);
 192:           else
 193:             rowModel.setSelectionInterval(lo_row, hi_row);
 194:         }
 195:       
 196:       // Update the columns
 197:       int lo_col = table.columnAtPoint(begin);
 198:       int hi_col = table.columnAtPoint(curr);
 199:       ListSelectionModel colModel = table.getColumnModel().
 200:         getSelectionModel();
 201:       if (lo_col != -1 && hi_col != -1)
 202:         {
 203:           if (controlPressed && colModel.getSelectionMode() != 
 204:               ListSelectionModel.SINGLE_SELECTION)
 205:             colModel.addSelectionInterval(lo_col, hi_col);
 206:           else
 207:             colModel.setSelectionInterval(lo_col, hi_col);
 208:         }
 209:     }
 210:     
 211:     /**
 212:      * For the double click, start the cell editor.
 213:      */
 214:     public void mouseClicked(MouseEvent e)
 215:     {
 216:       Point p = e.getPoint();
 217:       int row = table.rowAtPoint(p);
 218:       int col = table.columnAtPoint(p);
 219:       if (table.isCellEditable(row, col))
 220:         {
 221:           // If the cell editor is the default editor, we request the
 222:           // number of the required clicks from it. Otherwise,
 223:           // require two clicks (double click).
 224:           TableCellEditor editor = table.getCellEditor(row, col);
 225:           if (editor instanceof DefaultCellEditor)
 226:             {
 227:               DefaultCellEditor ce = (DefaultCellEditor) editor;
 228:               if (e.getClickCount() < ce.getClickCountToStart())
 229:                 return;
 230:             }
 231:           else if (e.getClickCount() < 2)
 232:             return;
 233:           table.editCellAt(row, col);
 234:         }
 235:     }
 236: 
 237:     public void mouseDragged(MouseEvent e) 
 238:     {
 239:       if (table.isEnabled())
 240:         {
 241:           curr = new Point(e.getX(), e.getY());
 242:           updateSelection(e.isControlDown());
 243:         }
 244:     }
 245: 
 246:     public void mouseEntered(MouseEvent e) 
 247:     {
 248:       // TODO: What should be done here, if anything?
 249:     }
 250: 
 251:     public void mouseExited(MouseEvent e) 
 252:     {
 253:       // TODO: What should be done here, if anything?
 254:     }
 255: 
 256:     public void mouseMoved(MouseEvent e) 
 257:     {
 258:       // TODO: What should be done here, if anything?
 259:     }
 260: 
 261:     public void mousePressed(MouseEvent e) 
 262:     {
 263:       if (table.isEnabled())
 264:         {
 265:           ListSelectionModel rowModel = table.getSelectionModel();
 266:           ListSelectionModel colModel = table.getColumnModel().getSelectionModel();
 267:           int rowLead = rowModel.getLeadSelectionIndex();
 268:           int colLead = colModel.getLeadSelectionIndex();
 269: 
 270:           begin = new Point(e.getX(), e.getY());
 271:           curr = new Point(e.getX(), e.getY());
 272:           //if control is pressed and the cell is already selected, deselect it
 273:           if (e.isControlDown() && table.
 274:               isCellSelected(table.rowAtPoint(begin),table.columnAtPoint(begin)))
 275:             {                                       
 276:               table.getSelectionModel().
 277:               removeSelectionInterval(table.rowAtPoint(begin), 
 278:                                       table.rowAtPoint(begin));
 279:               table.getColumnModel().getSelectionModel().
 280:               removeSelectionInterval(table.columnAtPoint(begin), 
 281:                                       table.columnAtPoint(begin));
 282:             }
 283:           else
 284:             updateSelection(e.isControlDown());
 285: 
 286:           // If we were editing, but the moved to another cell, stop editing
 287:           if (rowLead != rowModel.getLeadSelectionIndex() ||
 288:               colLead != colModel.getLeadSelectionIndex())
 289:             if (table.isEditing())
 290:               table.editingStopped(new ChangeEvent(e));
 291:         }
 292:     }
 293: 
 294:     public void mouseReleased(MouseEvent e) 
 295:     {
 296:       if (table.isEnabled())
 297:         {
 298:           begin = null;
 299:           curr = null;
 300:         }
 301:     }
 302:   }
 303: 
 304:   /**
 305:    * Listens for changes to the model property of the JTable and adjusts some
 306:    * settings.
 307:    *
 308:    * @author Roman Kennke (kennke@aicas.com)
 309:    */
 310:   private class PropertyChangeHandler implements PropertyChangeListener
 311:   {
 312:     /**
 313:      * Receives notification if one of the JTable's properties changes.
 314:      *
 315:      * @param ev the property change event
 316:      */
 317:     public void propertyChange(PropertyChangeEvent ev)
 318:     {
 319:       String propName = ev.getPropertyName();
 320:       if (propName.equals("model"))
 321:         {
 322:           ListSelectionModel rowSel = table.getSelectionModel();
 323:           rowSel.clearSelection();
 324:           ListSelectionModel colSel = table.getColumnModel().getSelectionModel();
 325:           colSel.clearSelection();
 326:           TableModel model = table.getModel();
 327: 
 328:           // Adjust lead and anchor selection indices of the row and column
 329:           // selection models.
 330:           if (model.getRowCount() > 0)
 331:             {
 332:               rowSel.setAnchorSelectionIndex(0);
 333:               rowSel.setLeadSelectionIndex(0);
 334:             }
 335:           else
 336:             {
 337:               rowSel.setAnchorSelectionIndex(-1);
 338:               rowSel.setLeadSelectionIndex(-1);
 339:             }
 340:           if (model.getColumnCount() > 0)
 341:             {
 342:               colSel.setAnchorSelectionIndex(0);
 343:               colSel.setLeadSelectionIndex(0);
 344:             }
 345:           else
 346:             {
 347:               colSel.setAnchorSelectionIndex(-1);
 348:               colSel.setLeadSelectionIndex(-1);
 349:             }
 350:         }
 351:     }
 352:   }
 353: 
 354:   protected FocusListener createFocusListener() 
 355:   {
 356:     return new FocusHandler();
 357:   }
 358: 
 359:   protected MouseInputListener createMouseInputListener() 
 360:   {
 361:     return new MouseInputHandler();
 362:   }
 363: 
 364: 
 365:   /**
 366:    * Creates and returns a key listener for the JTable.
 367:    *
 368:    * @return a key listener for the JTable
 369:    */
 370:   protected KeyListener createKeyListener()
 371:   {
 372:     return new KeyHandler();
 373:   }
 374: 
 375:   /**
 376:    * Return the maximum size of the table. The maximum height is the row 
 377:     * height times the number of rows. The maximum width is the sum of 
 378:     * the maximum widths of each column.
 379:     * 
 380:     *  @param comp the component whose maximum size is being queried,
 381:     *  this is ignored.
 382:     *  @return a Dimension object representing the maximum size of the table,
 383:     *  or null if the table has no elements.
 384:    */
 385:   public Dimension getMaximumSize(JComponent comp) 
 386:   {
 387:     int maxTotalColumnWidth = 0;
 388:     for (int i = 0; i < table.getColumnCount(); i++)
 389:       maxTotalColumnWidth += table.getColumnModel().getColumn(i).getMaxWidth();
 390:     if (maxTotalColumnWidth == 0 || table.getRowCount() == 0)
 391:       return null;
 392:     return new Dimension(maxTotalColumnWidth, table.getRowCount()*
 393:                          (table.getRowHeight()+table.getRowMargin()));
 394:   }
 395: 
 396:   /**
 397:    * Return the minimum size of the table. The minimum height is the row 
 398:     * height times the number of rows. The minimum width is the sum of 
 399:     * the minimum widths of each column.
 400:     * 
 401:     *  @param comp the component whose minimum size is being queried,
 402:     *  this is ignored.
 403:     *  @return a Dimension object representing the minimum size of the table,
 404:     *  or null if the table has no elements.
 405:    */
 406:   public Dimension getMinimumSize(JComponent comp) 
 407:   {
 408:     int minTotalColumnWidth = 0;
 409:     for (int i = 0; i < table.getColumnCount(); i++)
 410:       minTotalColumnWidth += table.getColumnModel().getColumn(i).getMinWidth();
 411:     if (minTotalColumnWidth == 0 || table.getRowCount() == 0)
 412:       return null;
 413:     return new Dimension(minTotalColumnWidth, table.getRowCount()*table.getRowHeight());
 414:   }
 415: 
 416:   public Dimension getPreferredSize(JComponent comp) 
 417:   {
 418:     int width = table.getColumnModel().getTotalColumnWidth();
 419:     int height = table.getRowCount() * (table.getRowHeight()+table.getRowMargin());
 420:     return new Dimension(width, height);
 421:   }
 422: 
 423:   protected void installDefaults() 
 424:   {
 425:     LookAndFeel.installColorsAndFont(table, "Table.background",
 426:                                      "Table.foreground", "Table.font");
 427:     table.setGridColor(UIManager.getColor("Table.gridColor"));
 428:     table.setSelectionForeground(UIManager.getColor("Table.selectionForeground"));
 429:     table.setSelectionBackground(UIManager.getColor("Table.selectionBackground"));
 430:     table.setOpaque(true);
 431:     rendererPane = new CellRendererPane();
 432:   }
 433: 
 434:   protected void installKeyboardActions() 
 435:   {
 436:     InputMap ancestorMap = (InputMap) UIManager.get("Table.ancestorInputMap");
 437:     InputMapUIResource parentInputMap = new InputMapUIResource();
 438:     // FIXME: The JDK uses a LazyActionMap for parentActionMap
 439:     ActionMap parentActionMap = new ActionMapUIResource();
 440:     action = new TableAction();
 441:     Object keys[] = ancestorMap.allKeys();
 442:     // Register key bindings in the UI InputMap-ActionMap pair
 443:     for (int i = 0; i < keys.length; i++)
 444:       {
 445:         KeyStroke stroke = (KeyStroke)keys[i];
 446:         String actionString = (String) ancestorMap.get(stroke);
 447: 
 448:         parentInputMap.put(KeyStroke.getKeyStroke(stroke.getKeyCode(),
 449:                                                   stroke.getModifiers()),
 450:                            actionString);
 451: 
 452:         parentActionMap.put (actionString, 
 453:                              new ActionListenerProxy (action, actionString));
 454: 
 455:       }
 456:     // Set the UI InputMap-ActionMap pair to be the parents of the
 457:     // JTable's InputMap-ActionMap pair
 458:     parentInputMap.setParent
 459:       (table.getInputMap
 460:        (JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent());
 461:     parentActionMap.setParent(table.getActionMap().getParent());
 462:     table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
 463:       setParent(parentInputMap);
 464:     table.getActionMap().setParent(parentActionMap);
 465:   }
 466: 
 467:   /**
 468:    * This class is used to mimmic the behaviour of the JDK when registering
 469:    * keyboard actions.  It is the same as the private class used in JComponent
 470:    * for the same reason.  This class receives an action event and dispatches
 471:    * it to the true receiver after altering the actionCommand property of the
 472:    * event.
 473:    */
 474:   private static class ActionListenerProxy
 475:     extends AbstractAction
 476:   {
 477:     ActionListener target;
 478:     String bindingCommandName;
 479: 
 480:     public ActionListenerProxy(ActionListener li, 
 481:                                String cmd)
 482:     {
 483:       target = li;
 484:       bindingCommandName = cmd;
 485:     }
 486: 
 487:     public void actionPerformed(ActionEvent e)
 488:     {
 489:       ActionEvent derivedEvent = new ActionEvent(e.getSource(),
 490:                                                  e.getID(),
 491:                                                  bindingCommandName,
 492:                                                  e.getModifiers());
 493:       target.actionPerformed(derivedEvent);
 494:     }
 495:   }
 496: 
 497:   /**
 498:    * This class implements the actions that we want to happen
 499:    * when specific keys are pressed for the JTable.  The actionPerformed
 500:    * method is called when a key that has been registered for the JTable
 501:    * is received.
 502:    */
 503:   class TableAction extends AbstractAction
 504:   {
 505:     /**
 506:      * What to do when this action is called.
 507:      *
 508:      * @param e the ActionEvent that caused this action.
 509:      */
 510:     public void actionPerformed (ActionEvent e)
 511:     {
 512:       DefaultListSelectionModel rowModel = (DefaultListSelectionModel) table.getSelectionModel();
 513:       DefaultListSelectionModel colModel = (DefaultListSelectionModel) table.getColumnModel().getSelectionModel();
 514: 
 515:       int rowLead = rowModel.getLeadSelectionIndex();
 516:       int rowMax = table.getModel().getRowCount() - 1;
 517:       
 518:       int colLead = colModel.getLeadSelectionIndex();
 519:       int colMax = table.getModel().getColumnCount() - 1;
 520:       
 521:       String command = e.getActionCommand();
 522:       
 523:       if (command.equals("selectPreviousRowExtendSelection"))
 524:         {
 525:           rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0));
 526:         }
 527:       else if (command.equals("selectLastColumn"))
 528:         {
 529:           colModel.setSelectionInterval(colMax, colMax);
 530:         }
 531:       else if (command.equals("startEditing"))
 532:         {
 533:           if (table.isCellEditable(rowLead, colLead))
 534:             table.editCellAt(rowLead,colLead);
 535:         }
 536:       else if (command.equals("selectFirstRowExtendSelection"))
 537:         {              
 538:           rowModel.setLeadSelectionIndex(0);
 539:         }
 540:       else if (command.equals("selectFirstColumn"))
 541:         {
 542:           colModel.setSelectionInterval(0, 0);
 543:         }
 544:       else if (command.equals("selectFirstColumnExtendSelection"))
 545:         {
 546:           colModel.setLeadSelectionIndex(0);
 547:         }      
 548:       else if (command.equals("selectLastRow"))
 549:         {
 550:           rowModel.setSelectionInterval(rowMax,rowMax);
 551:         }
 552:       else if (command.equals("selectNextRowExtendSelection"))
 553:         {
 554:           rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
 555:         }
 556:       else if (command.equals("selectFirstRow"))
 557:         {
 558:           rowModel.setSelectionInterval(0,0);
 559:         }
 560:       else if (command.equals("selectNextColumnExtendSelection"))
 561:         {
 562:           colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax));
 563:         }
 564:       else if (command.equals("selectLastColumnExtendSelection"))
 565:         {
 566:           colModel.setLeadSelectionIndex(colMax);
 567:         }
 568:       else if (command.equals("selectPreviousColumnExtendSelection"))
 569:         {
 570:           colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0));
 571:         }
 572:       else if (command.equals("selectNextRow"))
 573:         {
 574:           rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
 575:                                         Math.min(rowLead + 1, rowMax));
 576:         }
 577:       else if (command.equals("scrollUpExtendSelection"))
 578:         {
 579:           int target;
 580:           if (rowLead == getFirstVisibleRowIndex())
 581:             target = Math.max
 582:               (0, rowLead - (getLastVisibleRowIndex() - 
 583:                              getFirstVisibleRowIndex() + 1));
 584:           else
 585:             target = getFirstVisibleRowIndex();
 586:           
 587:           rowModel.setLeadSelectionIndex(target);
 588:           colModel.setLeadSelectionIndex(colLead);
 589:         }
 590:       else if (command.equals("selectPreviousRow"))
 591:         {
 592:           rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
 593:                                         Math.max(rowLead - 1, 0));
 594:         }
 595:       else if (command.equals("scrollRightChangeSelection"))
 596:         {
 597:           int target;
 598:           if (colLead == getLastVisibleColumnIndex())
 599:             target = Math.min
 600:               (colMax, colLead + (getLastVisibleColumnIndex() -
 601:                                   getFirstVisibleColumnIndex() + 1));
 602:           else
 603:             target = getLastVisibleColumnIndex();
 604:           
 605:           colModel.setSelectionInterval(target, target);
 606:           rowModel.setSelectionInterval(rowLead, rowLead);
 607:         }
 608:       else if (command.equals("selectPreviousColumn"))
 609:         {
 610:           colModel.setSelectionInterval(Math.max(colLead - 1, 0),
 611:                                         Math.max(colLead - 1, 0));
 612:         }
 613:       else if (command.equals("scrollLeftChangeSelection"))
 614:         {
 615:           int target;
 616:           if (colLead == getFirstVisibleColumnIndex())
 617:             target = Math.max
 618:               (0, colLead - (getLastVisibleColumnIndex() -
 619:                              getFirstVisibleColumnIndex() + 1));
 620:           else
 621:             target = getFirstVisibleColumnIndex();
 622:           
 623:           colModel.setSelectionInterval(target, target);
 624:           rowModel.setSelectionInterval(rowLead, rowLead);
 625:         }
 626:       else if (command.equals("clearSelection"))
 627:         {
 628:           table.clearSelection();
 629:         }
 630:       else if (command.equals("cancel"))
 631:         {
 632:           // FIXME: implement other parts of "cancel" like undo-ing last
 633:           // selection.  Right now it just calls editingCancelled if
 634:           // we're currently editing.
 635:           if (table.isEditing())
 636:             table.editingCanceled(new ChangeEvent("cancel"));
 637:         }
 638:       else if (command.equals("selectNextRowCell")
 639:                || command.equals("selectPreviousRowCell")
 640:                || command.equals("selectNextColumnCell")
 641:                || command.equals("selectPreviousColumnCell"))
 642:         {
 643:           // If nothing is selected, select the first cell in the table
 644:           if (table.getSelectedRowCount() == 0 && 
 645:               table.getSelectedColumnCount() == 0)
 646:             {
 647:               rowModel.setSelectionInterval(0, 0);
 648:               colModel.setSelectionInterval(0, 0);
 649:               return;
 650:             }
 651:           
 652:           // If the lead selection index isn't selected (ie a remove operation
 653:           // happened, then set the lead to the first selected cell in the
 654:           // table
 655:           if (!table.isCellSelected(rowLead, colLead))
 656:             {
 657:               rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(), 
 658:                                             rowModel.getMinSelectionIndex());
 659:               colModel.addSelectionInterval(colModel.getMinSelectionIndex(), 
 660:                                             colModel.getMinSelectionIndex());
 661:               return;
 662:             }
 663:           
 664:           // multRowsSelected and multColsSelected tell us if multiple rows or
 665:           // columns are selected, respectively
 666:           boolean multRowsSelected, multColsSelected;
 667:           multRowsSelected = table.getSelectedRowCount() > 1 &&
 668:             table.getRowSelectionAllowed();
 669:           
 670:           multColsSelected = table.getSelectedColumnCount() > 1 &&
 671:             table.getColumnSelectionAllowed();
 672:           
 673:           // If there is just one selection, select the next cell, and wrap
 674:           // when you get to the edges of the table.
 675:           if (!multColsSelected && !multRowsSelected)
 676:             {
 677:               if (command.indexOf("Column") != -1) 
 678:                 advanceSingleSelection(colModel, colMax, rowModel, rowMax, 
 679:                                        (command.equals
 680:                                         ("selectPreviousColumnCell")));
 681:               else
 682:                 advanceSingleSelection(rowModel, rowMax, colModel, colMax, 
 683:                                        (command.equals 
 684:                                         ("selectPreviousRowCell")));
 685:               return;
 686:             }
 687:           
 688:           
 689:           // rowMinSelected and rowMaxSelected are the minimum and maximum
 690:           // values respectively of selected cells in the row selection model
 691:           // Similarly for colMinSelected and colMaxSelected.
 692:           int rowMaxSelected = table.getRowSelectionAllowed() ? 
 693:             rowModel.getMaxSelectionIndex() : table.getModel().getRowCount() - 1;
 694:           int rowMinSelected = table.getRowSelectionAllowed() ? 
 695:             rowModel.getMinSelectionIndex() : 0; 
 696:           int colMaxSelected = table.getColumnSelectionAllowed() ? 
 697:             colModel.getMaxSelectionIndex() : 
 698:             table.getModel().getColumnCount() - 1;
 699:           int colMinSelected = table.getColumnSelectionAllowed() ? 
 700:             colModel.getMinSelectionIndex() : 0;
 701:           
 702:           // If there are multiple rows and columns selected, select the next
 703:           // cell and wrap at the edges of the selection.  
 704:           if (command.indexOf("Column") != -1) 
 705:             advanceMultipleSelection(colModel, colMinSelected, colMaxSelected, 
 706:                                      rowModel, rowMinSelected, rowMaxSelected, 
 707:                                      (command.equals
 708:                                       ("selectPreviousColumnCell")), true);
 709:           
 710:           else
 711:             advanceMultipleSelection(rowModel, rowMinSelected, rowMaxSelected, 
 712:                                      colModel, colMinSelected, colMaxSelected, 
 713:                                      (command.equals 
 714:                                       ("selectPreviousRowCell")), false);
 715:         }
 716:       else if (command.equals("selectNextColumn"))
 717:         {
 718:           colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
 719:                                         Math.min(colLead + 1, colMax));
 720:         }
 721:       else if (command.equals("scrollLeftExtendSelection"))
 722:         {
 723:           int target;
 724:           if (colLead == getFirstVisibleColumnIndex())
 725:             target = Math.max
 726:               (0, colLead - (getLastVisibleColumnIndex() -
 727:                              getFirstVisibleColumnIndex() + 1));
 728:           else
 729:             target = getFirstVisibleColumnIndex();
 730:           
 731:           colModel.setLeadSelectionIndex(target);
 732:           rowModel.setLeadSelectionIndex(rowLead);
 733:         }
 734:       else if (command.equals("scrollDownChangeSelection"))
 735:         {
 736:           int target;
 737:           if (rowLead == getLastVisibleRowIndex())
 738:             target = Math.min
 739:               (rowMax, rowLead + (getLastVisibleRowIndex() - 
 740:                                   getFirstVisibleRowIndex() + 1));
 741:           else
 742:             target = getLastVisibleRowIndex();
 743:           
 744:           rowModel.setSelectionInterval(target, target);
 745:           colModel.setSelectionInterval(colLead, colLead);
 746:         }
 747:       else if (command.equals("scrollRightExtendSelection"))
 748:         {
 749:           int target;
 750:           if (colLead == getLastVisibleColumnIndex())
 751:             target = Math.min
 752:               (colMax, colLead + (getLastVisibleColumnIndex() -
 753:                                   getFirstVisibleColumnIndex() + 1));
 754:           else
 755:             target = getLastVisibleColumnIndex();
 756:           
 757:           colModel.setLeadSelectionIndex(target);
 758:           rowModel.setLeadSelectionIndex(rowLead);
 759:         }
 760:       else if (command.equals("selectAll"))
 761:         {
 762:           table.selectAll();
 763:         }
 764:       else if (command.equals("selectLastRowExtendSelection"))
 765:         {
 766:           rowModel.setLeadSelectionIndex(rowMax);
 767:           colModel.setLeadSelectionIndex(colLead);
 768:         }
 769:       else if (command.equals("scrollDownExtendSelection"))
 770:         {
 771:           int target;
 772:           if (rowLead == getLastVisibleRowIndex())
 773:             target = Math.min
 774:               (rowMax, rowLead + (getLastVisibleRowIndex() - 
 775:                                   getFirstVisibleRowIndex() + 1));
 776:           else
 777:             target = getLastVisibleRowIndex();
 778:           
 779:           rowModel.setLeadSelectionIndex(target);
 780:           colModel.setLeadSelectionIndex(colLead);
 781:         }      
 782:       else if (command.equals("scrollUpChangeSelection"))
 783:         {
 784:           int target;
 785:           if (rowLead == getFirstVisibleRowIndex())
 786:             target = Math.max
 787:               (0, rowLead - (getLastVisibleRowIndex() - 
 788:                              getFirstVisibleRowIndex() + 1));
 789:           else
 790:             target = getFirstVisibleRowIndex();
 791:           
 792:           rowModel.setSelectionInterval(target, target);
 793:           colModel.setSelectionInterval(colLead, colLead);
 794:         }
 795:       else if (command.equals("selectNextRowChangeLead"))
 796:           {
 797:             if (rowModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
 798:               {
 799:                 // just "selectNextRow"
 800:                 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
 801:                                               Math.min(rowLead + 1, rowMax));
 802:                 colModel.setSelectionInterval(colLead,colLead);
 803:               }
 804:             else
 805:               rowModel.moveLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
 806:           }
 807:       else if (command.equals("selectPreviousRowChangeLead"))
 808:         {
 809:           if (rowModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
 810:             {
 811:               // just selectPreviousRow
 812:               rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
 813:                                             Math.min(rowLead -1, 0));
 814:               colModel.setSelectionInterval(colLead,colLead);
 815:             }
 816:           else
 817:             rowModel.moveLeadSelectionIndex(Math.max(rowLead - 1, 0));
 818:         }
 819:       else if (command.equals("selectNextColumnChangeLead"))
 820:         {
 821:           if (colModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)            
 822:             {
 823:               // just selectNextColumn
 824:               rowModel.setSelectionInterval(rowLead,rowLead);
 825:               colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
 826:                                             Math.min(colLead + 1, colMax));
 827:             }
 828:           else
 829:             colModel.moveLeadSelectionIndex(Math.min(colLead + 1, colMax));
 830:         }
 831:       else if (command.equals("selectPreviousColumnChangeLead"))
 832:         {
 833:           if (colModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)            
 834:             {
 835:               // just selectPreviousColumn
 836:               rowModel.setSelectionInterval(rowLead,rowLead);
 837:               colModel.setSelectionInterval(Math.max(colLead - 1, 0),
 838:                                             Math.max(colLead - 1, 0));
 839:               
 840:             }
 841:           else
 842:             colModel.moveLeadSelectionIndex(Math.max(colLead - 1, 0));
 843:         }
 844:       else if (command.equals("addToSelection"))
 845:           {
 846:             if (!table.isEditing())
 847:               {
 848:                 int oldRowAnchor = rowModel.getAnchorSelectionIndex();
 849:                 int oldColAnchor = colModel.getAnchorSelectionIndex();
 850:                 rowModel.addSelectionInterval(rowLead, rowLead);
 851:                 colModel.addSelectionInterval(colLead, colLead);
 852:                 rowModel.setAnchorSelectionIndex(oldRowAnchor);
 853:                 colModel.setAnchorSelectionIndex(oldColAnchor);
 854:               }
 855:           }
 856:       else if (command.equals("extendTo"))
 857:         {
 858:           rowModel.setSelectionInterval(rowModel.getAnchorSelectionIndex(),
 859:                                         rowLead);
 860:           colModel.setSelectionInterval(colModel.getAnchorSelectionIndex(),
 861:                                         colLead);
 862:         }
 863:       else if (command.equals("toggleAndAnchor"))
 864:         {
 865:           if (rowModel.isSelectedIndex(rowLead))
 866:             rowModel.removeSelectionInterval(rowLead, rowLead);
 867:           else
 868:             rowModel.addSelectionInterval(rowLead, rowLead);
 869:           
 870:           if (colModel.isSelectedIndex(colLead))
 871:             colModel.removeSelectionInterval(colLead, colLead);
 872:           else
 873:             colModel.addSelectionInterval(colLead, colLead);
 874:           
 875:           rowModel.setAnchorSelectionIndex(rowLead);
 876:           colModel.setAnchorSelectionIndex(colLead);
 877:         }
 878:       else if (command.equals("stopEditing"))
 879:         {
 880:           table.editingStopped(new ChangeEvent(command));
 881:         }
 882:       else 
 883:         {
 884:           // If we're here that means we bound this TableAction class
 885:           // to a keyboard input but we either want to ignore that input
 886:           // or we just haven't implemented its action yet.
 887:           
 888:           // Uncomment the following line to print the names of unused bindings
 889:           // when their keys are pressed
 890:           
 891:           // System.out.println ("not implemented: "+e.getActionCommand());
 892:         }
 893: 
 894:       // Any commands whose keyStrokes should be used by the Editor should not
 895:       // cause editing to be stopped: ie, the SPACE sends "addToSelection" but 
 896:       // if the table is in editing mode, the space should not cause us to stop
 897:       // editing because it should be used by the Editor.
 898:       if (table.isEditing() && command != "startEditing"
 899:           && command != "addToSelection")
 900:         table.editingStopped(new ChangeEvent("update"));
 901:             
 902:       table.scrollRectToVisible
 903:         (table.getCellRect(rowModel.getLeadSelectionIndex(), 
 904:                            colModel.getLeadSelectionIndex(), false));
 905:     }
 906:     
 907:     /**
 908:      * Returns the column index of the first visible column.
 909:      * @return the column index of the first visible column.
 910:      */
 911:     int getFirstVisibleColumnIndex()
 912:     {
 913:       ComponentOrientation or = table.getComponentOrientation();
 914:       Rectangle r = table.getVisibleRect();
 915:       if (!or.isLeftToRight())
 916:         r.translate((int) r.getWidth() - 1, 0);
 917:       return table.columnAtPoint(r.getLocation());
 918:     }
 919:     
 920:     /**
 921:      * Returns the column index of the last visible column.
 922:      *
 923:      */
 924:     int getLastVisibleColumnIndex()
 925:     {
 926:       ComponentOrientation or = table.getComponentOrientation();
 927:       Rectangle r = table.getVisibleRect();
 928:       if (or.isLeftToRight())
 929:         r.translate((int) r.getWidth() - 1, 0);
 930:       return table.columnAtPoint(r.getLocation());      
 931:     }
 932:     
 933:     /**
 934:      * Returns the row index of the first visible row.
 935:      *
 936:      */
 937:     int getFirstVisibleRowIndex()
 938:     {
 939:       ComponentOrientation or = table.getComponentOrientation();
 940:       Rectangle r = table.getVisibleRect();
 941:       if (!or.isLeftToRight())
 942:         r.translate((int) r.getWidth() - 1, 0);
 943:       return table.rowAtPoint(r.getLocation());
 944:     }
 945:     
 946:     /**
 947:      * Returns the row index of the last visible row.
 948:      *
 949:      */
 950:     int getLastVisibleRowIndex()
 951:     {
 952:       ComponentOrientation or = table.getComponentOrientation();
 953:       Rectangle r = table.getVisibleRect();
 954:       r.translate(0, (int) r.getHeight() - 1);
 955:       if (or.isLeftToRight())
 956:         r.translate((int) r.getWidth() - 1, 0);
 957:       // The next if makes sure that we don't return -1 simply because
 958:       // there is white space at the bottom of the table (ie, the display
 959:       // area is larger than the table)
 960:       if (table.rowAtPoint(r.getLocation()) == -1)
 961:         {
 962:           if (getFirstVisibleRowIndex() == -1)
 963:             return -1;
 964:           else
 965:             return table.getModel().getRowCount() - 1;
 966:         }
 967:       return table.rowAtPoint(r.getLocation());
 968:     }
 969: 
 970:     /**
 971:      * A helper method for the key bindings.  Used because the actions
 972:      * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
 973:      *
 974:      * Selects the next (previous if SHIFT pressed) column for TAB, or row for
 975:      * ENTER from within the currently selected cells.
 976:      *
 977:      * @param firstModel the ListSelectionModel for columns (TAB) or
 978:      * rows (ENTER)
 979:      * @param firstMin the first selected index in firstModel
 980:      * @param firstMax the last selected index in firstModel
 981:      * @param secondModel the ListSelectionModel for rows (TAB) or 
 982:      * columns (ENTER)
 983:      * @param secondMin the first selected index in secondModel
 984:      * @param secondMax the last selected index in secondModel
 985:      * @param reverse true if shift was held for the event
 986:      * @param eventIsTab true if TAB was pressed, false if ENTER pressed
 987:      */
 988:     void advanceMultipleSelection (ListSelectionModel firstModel, int firstMin,
 989:                                    int firstMax, ListSelectionModel secondModel, 
 990:                                    int secondMin, int secondMax, boolean reverse,
 991:                                    boolean eventIsTab)
 992:     {
 993:       // If eventIsTab, all the "firsts" correspond to columns, otherwise, to rows
 994:       // "seconds" correspond to the opposite
 995:       int firstLead = firstModel.getLeadSelectionIndex();
 996:       int secondLead = secondModel.getLeadSelectionIndex();
 997:       int numFirsts = eventIsTab ? 
 998:         table.getModel().getColumnCount() : table.getModel().getRowCount();
 999:       int numSeconds = eventIsTab ? 
1000:         table.getModel().getRowCount() : table.getModel().getColumnCount();
1001: 
1002:       // check if we have to wrap the "firsts" around, going to the other side
1003:       if ((firstLead == firstMax && !reverse) || 
1004:           (reverse && firstLead == firstMin))
1005:         {
1006:           firstModel.addSelectionInterval(reverse ? firstMax : firstMin, 
1007:                                           reverse ? firstMax : firstMin);
1008:           
1009:           // check if we have to wrap the "seconds"
1010:           if ((secondLead == secondMax && !reverse) || 
1011:               (reverse && secondLead == secondMin))
1012:             secondModel.addSelectionInterval(reverse ? secondMax : secondMin, 
1013:                                              reverse ? secondMax : secondMin);
1014: 
1015:           // if we're not wrapping the seconds, we have to find out where we
1016:           // are within the secondModel and advance to the next cell (or 
1017:           // go back to the previous cell if reverse == true)
1018:           else
1019:             {
1020:               int[] secondsSelected;
1021:               if (eventIsTab && table.getRowSelectionAllowed() || 
1022:                   !eventIsTab && table.getColumnSelectionAllowed())
1023:                 secondsSelected = eventIsTab ? 
1024:                   table.getSelectedRows() : table.getSelectedColumns();
1025:               else
1026:                 {
1027:                   // if row selection is not allowed, then the entire column gets
1028:                   // selected when you click on it, so consider ALL rows selected
1029:                   secondsSelected = new int[numSeconds];
1030:                   for (int i = 0; i < numSeconds; i++)
1031:                   secondsSelected[i] = i;
1032:                 }
1033: 
1034:               // and now find the "next" index within the model
1035:               int secondIndex = reverse ? secondsSelected.length - 1 : 0;
1036:               if (!reverse)
1037:                 while (secondsSelected[secondIndex] <= secondLead)
1038:                   secondIndex++;
1039:               else
1040:                 while (secondsSelected[secondIndex] >= secondLead)
1041:                   secondIndex--;
1042:               
1043:               // and select it - updating the lead selection index
1044:               secondModel.addSelectionInterval(secondsSelected[secondIndex], 
1045:                                                secondsSelected[secondIndex]);
1046:             }
1047:         }
1048:       // We didn't have to wrap the firsts, so just find the "next" first
1049:       // and select it, we don't have to change "seconds"
1050:       else
1051:         {
1052:           int[] firstsSelected;
1053:           if (eventIsTab && table.getColumnSelectionAllowed() || 
1054:               !eventIsTab && table.getRowSelectionAllowed())
1055:             firstsSelected = eventIsTab ? 
1056:               table.getSelectedColumns() : table.getSelectedRows();
1057:           else
1058:             {
1059:               // if selection not allowed, consider ALL firsts to be selected
1060:               firstsSelected = new int[numFirsts];
1061:               for (int i = 0; i < numFirsts; i++)
1062:                 firstsSelected[i] = i;
1063:             }
1064:           int firstIndex = reverse ? firstsSelected.length - 1 : 0;
1065:           if (!reverse)
1066:             while (firstsSelected[firstIndex] <= firstLead)
1067:               firstIndex++;
1068:           else 
1069:             while (firstsSelected[firstIndex] >= firstLead)
1070:               firstIndex--;
1071:           firstModel.addSelectionInterval(firstsSelected[firstIndex], 
1072:                                           firstsSelected[firstIndex]);
1073:           secondModel.addSelectionInterval(secondLead, secondLead);
1074:         }
1075:     }
1076:     
1077:     /** 
1078:      * A helper method for the key  bindings. Used because the actions
1079:      * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
1080:      *
1081:      * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER)
1082:      * in the table, changing the current selection.  All cells in the table
1083:      * are eligible, not just the ones that are currently selected.
1084:      * @param firstModel the ListSelectionModel for columns (TAB) or rows
1085:      * (ENTER)
1086:      * @param firstMax the last index in firstModel
1087:      * @param secondModel the ListSelectionModel for rows (TAB) or columns
1088:      * (ENTER)
1089:      * @param secondMax the last index in secondModel
1090:      * @param reverse true if SHIFT was pressed for the event
1091:      */
1092: 
1093:     void advanceSingleSelection (ListSelectionModel firstModel, int firstMax, 
1094:                                  ListSelectionModel secondModel, int secondMax, 
1095:                                  boolean reverse)
1096:     {
1097:       // for TABs, "first" corresponds to columns and "seconds" to rows.
1098:       // the opposite is true for ENTERs
1099:       int firstLead = firstModel.getLeadSelectionIndex();
1100:       int secondLead = secondModel.getLeadSelectionIndex();
1101:       
1102:       // if we are going backwards subtract 2 because we later add 1
1103:       // for a net change of -1
1104:       if (reverse && (firstLead == 0))
1105:         {
1106:           // check if we have to wrap around
1107:           if (secondLead == 0)
1108:             secondLead += secondMax + 1;
1109:           secondLead -= 2;
1110:         }
1111:       
1112:       // do we have to wrap the "seconds"?
1113:       if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax))
1114:         secondModel.setSelectionInterval((secondLead + 1)%(secondMax + 1), 
1115:                                          (secondLead + 1)%(secondMax + 1));
1116:       // if not, just reselect the current lead
1117:       else
1118:         secondModel.setSelectionInterval(secondLead, secondLead);
1119:       
1120:       // if we are going backwards, subtract 2  because we add 1 later
1121:       // for net change of -1
1122:       if (reverse)
1123:         {
1124:           // check for wraparound
1125:           if (firstLead == 0)
1126:             firstLead += firstMax + 1;
1127:           firstLead -= 2;
1128:         }
1129:       // select the next "first"
1130:       firstModel.setSelectionInterval ((firstLead + 1)%(firstMax + 1), 
1131:                                        (firstLead + 1)%(firstMax + 1));
1132:     }
1133:   }
1134: 
1135:   protected void installListeners() 
1136:   {
1137:     if (focusListener == null)
1138:       focusListener = createFocusListener();
1139:     table.addFocusListener(focusListener);
1140:     if (keyListener == null)
1141:       keyListener = createKeyListener();
1142:     table.addKeyListener(keyListener);
1143:     if (mouseInputListener == null)
1144:       mouseInputListener = createMouseInputListener();
1145:     table.addMouseListener(mouseInputListener);    
1146:     table.addMouseMotionListener(mouseInputListener);
1147:     if (propertyChangeListener == null)
1148:       propertyChangeListener = new PropertyChangeHandler();
1149:     table.addPropertyChangeListener(propertyChangeListener);
1150:   }
1151: 
1152:   protected void uninstallDefaults() 
1153:   {
1154:     // TODO: this method used to do the following which is not
1155:     // quite right (at least it breaks apps that run fine with the
1156:     // JDK):
1157:     //
1158:     // table.setFont(null);
1159:     // table.setGridColor(null);
1160:     // table.setForeground(null);
1161:     // table.setBackground(null);
1162:     // table.setSelectionForeground(null);
1163:     // table.setSelectionBackground(null);
1164:     //
1165:     // This would leave the component in a corrupt state, which is
1166:     // not acceptable. A possible solution would be to have component
1167:     // level defaults installed, that get overridden by the UI defaults
1168:     // and get restored in this method. I am not quite sure about this
1169:     // though. / Roman Kennke
1170:   }
1171: 
1172:   protected void uninstallKeyboardActions() 
1173:     throws NotImplementedException
1174:   {
1175:     // TODO: Implement this properly.
1176:   }
1177: 
1178:   protected void uninstallListeners() 
1179:   {
1180:     table.removeFocusListener(focusListener);  
1181:     table.removeKeyListener(keyListener);
1182:     table.removeMouseListener(mouseInputListener);    
1183:     table.removeMouseMotionListener(mouseInputListener);
1184:     table.removePropertyChangeListener(propertyChangeListener);
1185:     propertyChangeListener = null;
1186:   }
1187: 
1188:   public void installUI(JComponent comp) 
1189:   {
1190:     table = (JTable)comp;
1191:     installDefaults();
1192:     installKeyboardActions();
1193:     installListeners();
1194:   }
1195: 
1196:   public void uninstallUI(JComponent c) 
1197:   {
1198:     uninstallListeners();
1199:     uninstallKeyboardActions();
1200:     uninstallDefaults();    
1201:   }
1202: 
1203:   /**
1204:    * Paints a single cell in the table.
1205:    *
1206:    * @param g The graphics context to paint in
1207:    * @param row The row number to paint
1208:    * @param col The column number to paint
1209:    * @param bounds The bounds of the cell to paint, assuming a coordinate
1210:    * system beginning at <code>(0,0)</code> in the upper left corner of the
1211:    * table
1212:    * @param rend A cell renderer to paint with
1213:    */
1214:   void paintCell(Graphics g, int row, int col, Rectangle bounds,
1215:                  TableCellRenderer rend)
1216:   {
1217:     Component comp = table.prepareRenderer(rend, row, col);
1218:     rendererPane.paintComponent(g, comp, table, bounds);
1219:   }
1220:   
1221:   /**
1222:    * Paint the associated table.
1223:    */
1224:   public void paint(Graphics gfx, JComponent ignored) 
1225:   {
1226:     int ncols = table.getColumnCount();
1227:     int nrows = table.getRowCount();
1228:     if (nrows == 0 || ncols == 0)
1229:       return;
1230: 
1231:     Rectangle clip = gfx.getClipBounds();
1232: 
1233:     // Determine the range of cells that are within the clip bounds.
1234:     Point p1 = new Point(clip.x, clip.y);
1235:     int c0 = table.columnAtPoint(p1);
1236:     if (c0 == -1)
1237:       c0 = 0;
1238:     int r0 = table.rowAtPoint(p1);
1239:     if (r0 == -1)
1240:       r0 = 0;
1241:     Point p2 = new Point(clip.x + clip.width, clip.y + clip.height);
1242:     int cn = table.columnAtPoint(p2);
1243:     if (cn == -1)
1244:       cn = table.getColumnCount() - 1;
1245:     int rn = table.rowAtPoint(p2);
1246:     if (rn == -1)
1247:       rn = table.getRowCount() - 1;
1248: 
1249:     int columnMargin = table.getColumnModel().getColumnMargin();
1250:     int rowMargin = table.getRowMargin();
1251: 
1252:     TableColumnModel cmodel = table.getColumnModel();
1253:     int [] widths = new int[cn+1];
1254:     for (int i = c0; i <=cn ; i++)
1255:       {
1256:         widths[i] = cmodel.getColumn(i).getWidth() - columnMargin;
1257:       }
1258:     
1259:     Rectangle bounds = table.getCellRect(r0, c0, false);
1260: 
1261:     // The left boundary of the area being repainted.
1262:     int left = bounds.x;
1263:     
1264:     // The top boundary of the area being repainted.
1265:     int top = bounds.y;
1266:     
1267:     // The bottom boundary of the area being repainted.
1268:     int bottom;
1269:     
1270:     // paint the cell contents
1271:     Color grid = table.getGridColor();    
1272:     for (int r = r0; r <= rn; ++r)
1273:       {
1274:         for (int c = c0; c <= cn; ++c)
1275:           {
1276:             bounds.width = widths[c];
1277:             paintCell(gfx, r, c, bounds, table.getCellRenderer(r, c));
1278:             bounds.x += widths[c] + columnMargin;
1279:           }
1280:         bounds.x = left;
1281:         bounds.y += table.getRowHeight(r) + rowMargin;
1282:         // Update row height for tables with custom heights.
1283:         bounds.height = table.getRowHeight(r + 1);
1284:       }
1285:     
1286:     bottom = bounds.y - rowMargin;
1287: 
1288:     // paint vertical grid lines
1289:     if (grid != null && table.getShowVerticalLines())
1290:       {    
1291:         Color save = gfx.getColor();
1292:         gfx.setColor(grid);
1293:         int x = left - columnMargin;
1294:         for (int c = c0; c <= cn; ++c)
1295:           {
1296:             // The vertical grid is draw right from the cells, so we 
1297:             // add before drawing.
1298:             x += widths[c] + columnMargin;
1299:             gfx.drawLine(x, top, x, bottom);
1300:           }
1301:         gfx.setColor(save);
1302:       }
1303: 
1304:     // paint horizontal grid lines    
1305:     if (grid != null && table.getShowHorizontalLines())
1306:       {    
1307:         Color save = gfx.getColor();
1308:         gfx.setColor(grid);
1309:         int y = top - rowMargin;
1310:         for (int r = r0; r <= rn; ++r)
1311:           {
1312:             // The horizontal grid is draw below the cells, so we 
1313:             // add before drawing.
1314:             y += table.getRowHeight(r) + rowMargin;
1315:             gfx.drawLine(left, y, p2.x, y);
1316:           }
1317:         gfx.setColor(save);
1318:       }
1319:   }
1320: }