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