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