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:     for (int i = 0; i < table.getColumnCount(); i++)
 447:       {
 448:         TableColumn col = table.getColumnModel().getColumn(i);
 449:         prefTotalColumnWidth += col.getPreferredWidth();
 450:       }
 451:     return new Dimension(prefTotalColumnWidth, getHeight());
 452:   }
 453: 
 454:   /**
 455:    * Returns the table height. This helper method is used by
 456:    * {@link #getMinimumSize(JComponent)}, {@link #getPreferredSize(JComponent)}
 457:    * and {@link #getMaximumSize(JComponent)} to determine the table height.
 458:    *
 459:    * @return the table height
 460:    */
 461:   private int getHeight()
 462:   {
 463:     int height = 0;
 464:     int rowCount = table.getRowCount(); 
 465:     if (rowCount > 0 && table.getColumnCount() > 0)
 466:       {
 467:         Rectangle r = table.getCellRect(rowCount - 1, 0, true);
 468:         height = r.y + r.height;
 469:       }
 470:     return height;
 471:   }
 472: 
 473:   protected void installDefaults() 
 474:   {
 475:     LookAndFeel.installColorsAndFont(table, "Table.background",
 476:                                      "Table.foreground", "Table.font");
 477:     table.setGridColor(UIManager.getColor("Table.gridColor"));
 478:     table.setSelectionForeground(UIManager.getColor("Table.selectionForeground"));
 479:     table.setSelectionBackground(UIManager.getColor("Table.selectionBackground"));
 480:     table.setOpaque(true);
 481:   }
 482: 
 483:   /**
 484:    * Installs keyboard actions on the table.
 485:    */
 486:   protected void installKeyboardActions() 
 487:   {
 488:     // Install the input map.
 489:     InputMap inputMap =
 490:       (InputMap) SharedUIDefaults.get("Table.ancestorInputMap");
 491:     SwingUtilities.replaceUIInputMap(table,
 492:                                  JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
 493:                                  inputMap);
 494: 
 495:     // FIXME: The JDK uses a LazyActionMap for parentActionMap
 496:     SwingUtilities.replaceUIActionMap(table, getActionMap());
 497: 
 498:   }
 499: 
 500:   /**
 501:    * Fetches the action map from  the UI defaults, or create a new one
 502:    * if the action map hasn't been initialized.
 503:    *
 504:    * @return the action map
 505:    */
 506:   private ActionMap getActionMap()
 507:   {
 508:     ActionMap am = (ActionMap) UIManager.get("Table.actionMap");
 509:     if (am == null)
 510:       {
 511:         am = createDefaultActions();
 512:         UIManager.getLookAndFeelDefaults().put("Table.actionMap", am);
 513:       }
 514:     return am;
 515:   }
 516: 
 517:   private ActionMap createDefaultActions()
 518:   {
 519:     ActionMapUIResource am = new ActionMapUIResource();
 520:     Action action = new TableAction();
 521: 
 522:     am.put("cut", TransferHandler.getCutAction());
 523:     am.put("copy", TransferHandler.getCopyAction());
 524:     am.put("paste", TransferHandler.getPasteAction());
 525: 
 526:     am.put("cancel", action);
 527:     am.put("selectAll", action);
 528:     am.put("clearSelection", action);
 529:     am.put("startEditing", action);
 530: 
 531:     am.put("selectNextRow", action);
 532:     am.put("selectNextRowCell", action);
 533:     am.put("selectNextRowExtendSelection", action);
 534:     am.put("selectNextRowChangeLead", action);
 535: 
 536:     am.put("selectPreviousRow", action);
 537:     am.put("selectPreviousRowCell", action);
 538:     am.put("selectPreviousRowExtendSelection", action);
 539:     am.put("selectPreviousRowChangeLead", action);
 540: 
 541:     am.put("selectNextColumn", action);
 542:     am.put("selectNextColumnCell", action);
 543:     am.put("selectNextColumnExtendSelection", action);
 544:     am.put("selectNextColumnChangeLead", action);
 545: 
 546:     am.put("selectPreviousColumn", action);
 547:     am.put("selectPreviousColumnCell", action);
 548:     am.put("selectPreviousColumnExtendSelection", action);
 549:     am.put("selectPreviousColumnChangeLead", action);
 550: 
 551:     am.put("scrollLeftChangeSelection", action);
 552:     am.put("scrollLeftExtendSelection", action);
 553:     am.put("scrollRightChangeSelection", action);
 554:     am.put("scrollRightExtendSelection", action);
 555: 
 556:     am.put("scrollUpChangeSelection", action);
 557:     am.put("scrollUpExtendSelection", action);
 558:     am.put("scrollDownChangeSelection", action);
 559:     am.put("scrolldownExtendSelection", action);
 560: 
 561:     am.put("selectFirstColumn", action);
 562:     am.put("selectFirstColumnExtendSelection", action);
 563:     am.put("selectLastColumn", action);
 564:     am.put("selectLastColumnExtendSelection", action);
 565: 
 566:     am.put("selectFirstRow", action);
 567:     am.put("selectFirstRowExtendSelection", action);
 568:     am.put("selectLastRow", action);
 569:     am.put("selectLastRowExtendSelection", action);
 570: 
 571:     am.put("addToSelection", action);
 572:     am.put("toggleAndAnchor", action);
 573:     am.put("extendTo", action);
 574:     am.put("moveSelectionTo", action);
 575: 
 576:     return am;
 577:   }
 578: 
 579:   /**
 580:    * This class implements the actions that we want to happen
 581:    * when specific keys are pressed for the JTable.  The actionPerformed
 582:    * method is called when a key that has been registered for the JTable
 583:    * is received.
 584:    */
 585:   private static class TableAction
 586:     extends AbstractAction
 587:   {
 588:     /**
 589:      * What to do when this action is called.
 590:      *
 591:      * @param e the ActionEvent that caused this action.
 592:      */
 593:     public void actionPerformed(ActionEvent e)
 594:     {
 595:       JTable table = (JTable) e.getSource();
 596: 
 597:       DefaultListSelectionModel rowModel 
 598:           = (DefaultListSelectionModel) table.getSelectionModel();
 599:       DefaultListSelectionModel colModel 
 600:           = (DefaultListSelectionModel) table.getColumnModel().getSelectionModel();
 601: 
 602:       int rowLead = rowModel.getLeadSelectionIndex();
 603:       int rowMax = table.getModel().getRowCount() - 1;
 604:       
 605:       int colLead = colModel.getLeadSelectionIndex();
 606:       int colMax = table.getModel().getColumnCount() - 1;
 607: 
 608:       // The command with which the action has been called is stored
 609:       // in this undocumented action value. This allows us to have only
 610:       // one Action instance to serve all keyboard input for JTable.
 611:       String command = (String) getValue("__command__");
 612:       if (command.equals("selectPreviousRowExtendSelection"))
 613:         {
 614:           rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0));
 615:         }
 616:       else if (command.equals("selectLastColumn"))
 617:         {
 618:           colModel.setSelectionInterval(colMax, colMax);
 619:         }
 620:       else if (command.equals("startEditing"))
 621:         {
 622:           if (table.isCellEditable(rowLead, colLead))
 623:             table.editCellAt(rowLead, colLead);
 624:         }
 625:       else if (command.equals("selectFirstRowExtendSelection"))
 626:         {              
 627:           rowModel.setLeadSelectionIndex(0);
 628:         }
 629:       else if (command.equals("selectFirstColumn"))
 630:         {
 631:           colModel.setSelectionInterval(0, 0);
 632:         }
 633:       else if (command.equals("selectFirstColumnExtendSelection"))
 634:         {
 635:           colModel.setLeadSelectionIndex(0);
 636:         }      
 637:       else if (command.equals("selectLastRow"))
 638:         {
 639:           rowModel.setSelectionInterval(rowMax, rowMax);
 640:         }
 641:       else if (command.equals("selectNextRowExtendSelection"))
 642:         {
 643:           rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
 644:         }
 645:       else if (command.equals("selectFirstRow"))
 646:         {
 647:           rowModel.setSelectionInterval(0, 0);
 648:         }
 649:       else if (command.equals("selectNextColumnExtendSelection"))
 650:         {
 651:           colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax));
 652:         }
 653:       else if (command.equals("selectLastColumnExtendSelection"))
 654:         {
 655:           colModel.setLeadSelectionIndex(colMax);
 656:         }
 657:       else if (command.equals("selectPreviousColumnExtendSelection"))
 658:         {
 659:           colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0));
 660:         }
 661:       else if (command.equals("selectNextRow"))
 662:         {
 663:           rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
 664:                                         Math.min(rowLead + 1, rowMax));
 665:         }
 666:       else if (command.equals("scrollUpExtendSelection"))
 667:         {
 668:           int target;
 669:           if (rowLead == getFirstVisibleRowIndex(table))
 670:             target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) 
 671:                 - getFirstVisibleRowIndex(table) + 1));
 672:           else
 673:             target = getFirstVisibleRowIndex(table);
 674:           
 675:           rowModel.setLeadSelectionIndex(target);
 676:           colModel.setLeadSelectionIndex(colLead);
 677:         }
 678:       else if (command.equals("selectPreviousRow"))
 679:         {
 680:           rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
 681:                                         Math.max(rowLead - 1, 0));
 682:         }
 683:       else if (command.equals("scrollRightChangeSelection"))
 684:         {
 685:           int target;
 686:           if (colLead == getLastVisibleColumnIndex(table))
 687:             target = Math.min(colMax, colLead
 688:                               + (getLastVisibleColumnIndex(table)
 689:                               - getFirstVisibleColumnIndex(table) + 1));
 690:           else
 691:             target = getLastVisibleColumnIndex(table);
 692:           
 693:           colModel.setSelectionInterval(target, target);
 694:           rowModel.setSelectionInterval(rowLead, rowLead);
 695:         }
 696:       else if (command.equals("selectPreviousColumn"))
 697:         {
 698:           colModel.setSelectionInterval(Math.max(colLead - 1, 0),
 699:                                         Math.max(colLead - 1, 0));
 700:         }
 701:       else if (command.equals("scrollLeftChangeSelection"))
 702:         {
 703:           int target;
 704:           if (colLead == getFirstVisibleColumnIndex(table))
 705:             target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) 
 706:                                  - getFirstVisibleColumnIndex(table) + 1));
 707:           else
 708:             target = getFirstVisibleColumnIndex(table);
 709:           
 710:           colModel.setSelectionInterval(target, target);
 711:           rowModel.setSelectionInterval(rowLead, rowLead);
 712:         }
 713:       else if (command.equals("clearSelection"))
 714:         {
 715:           table.clearSelection();
 716:         }
 717:       else if (command.equals("cancel"))
 718:         {
 719:           // FIXME: implement other parts of "cancel" like undo-ing last
 720:           // selection.  Right now it just calls editingCancelled if
 721:           // we're currently editing.
 722:           if (table.isEditing())
 723:             table.editingCanceled(new ChangeEvent("cancel"));
 724:         }
 725:       else if (command.equals("selectNextRowCell")
 726:                || command.equals("selectPreviousRowCell")
 727:                || command.equals("selectNextColumnCell")
 728:                || command.equals("selectPreviousColumnCell"))
 729:         {
 730:           // If nothing is selected, select the first cell in the table
 731:           if (table.getSelectedRowCount() == 0 && 
 732:               table.getSelectedColumnCount() == 0)
 733:             {
 734:               rowModel.setSelectionInterval(0, 0);
 735:               colModel.setSelectionInterval(0, 0);
 736:               return;
 737:             }
 738:           
 739:           // If the lead selection index isn't selected (ie a remove operation
 740:           // happened, then set the lead to the first selected cell in the
 741:           // table
 742:           if (!table.isCellSelected(rowLead, colLead))
 743:             {
 744:               rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(), 
 745:                                             rowModel.getMinSelectionIndex());
 746:               colModel.addSelectionInterval(colModel.getMinSelectionIndex(), 
 747:                                             colModel.getMinSelectionIndex());
 748:               return;
 749:             }
 750:           
 751:           // multRowsSelected and multColsSelected tell us if multiple rows or
 752:           // columns are selected, respectively
 753:           boolean multRowsSelected, multColsSelected;
 754:           multRowsSelected = table.getSelectedRowCount() > 1 &&
 755:             table.getRowSelectionAllowed();
 756:           
 757:           multColsSelected = table.getSelectedColumnCount() > 1 &&
 758:             table.getColumnSelectionAllowed();
 759:           
 760:           // If there is just one selection, select the next cell, and wrap
 761:           // when you get to the edges of the table.
 762:           if (!multColsSelected && !multRowsSelected)
 763:             {
 764:               if (command.indexOf("Column") != -1) 
 765:                 advanceSingleSelection(colModel, colMax, rowModel, rowMax, 
 766:                     command.equals("selectPreviousColumnCell"));
 767:               else
 768:                 advanceSingleSelection(rowModel, rowMax, colModel, colMax, 
 769:                     command.equals("selectPreviousRowCell"));
 770:               return;
 771:             }
 772:           
 773:           
 774:           // rowMinSelected and rowMaxSelected are the minimum and maximum
 775:           // values respectively of selected cells in the row selection model
 776:           // Similarly for colMinSelected and colMaxSelected.
 777:           int rowMaxSelected = table.getRowSelectionAllowed() ? 
 778:             rowModel.getMaxSelectionIndex() : table.getModel().getRowCount() - 1;
 779:           int rowMinSelected = table.getRowSelectionAllowed() ? 
 780:             rowModel.getMinSelectionIndex() : 0; 
 781:           int colMaxSelected = table.getColumnSelectionAllowed() ? 
 782:             colModel.getMaxSelectionIndex() : 
 783:             table.getModel().getColumnCount() - 1;
 784:           int colMinSelected = table.getColumnSelectionAllowed() ? 
 785:             colModel.getMinSelectionIndex() : 0;
 786:           
 787:           // If there are multiple rows and columns selected, select the next
 788:           // cell and wrap at the edges of the selection.  
 789:           if (command.indexOf("Column") != -1) 
 790:             advanceMultipleSelection(table, colModel, colMinSelected,
 791:                                      colMaxSelected, rowModel, rowMinSelected,
 792:                                      rowMaxSelected,
 793:                                     command.equals("selectPreviousColumnCell"),
 794:                                     true);
 795:           
 796:           else
 797:             advanceMultipleSelection(table, rowModel, rowMinSelected,
 798:                                      rowMaxSelected, colModel, colMinSelected,
 799:                                      colMaxSelected,
 800:                                      command.equals("selectPreviousRowCell"),
 801:                                      false);
 802:         }
 803:       else if (command.equals("selectNextColumn"))
 804:         {
 805:           colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
 806:                                         Math.min(colLead + 1, colMax));
 807:         }
 808:       else if (command.equals("scrollLeftExtendSelection"))
 809:         {
 810:           int target;
 811:           if (colLead == getFirstVisibleColumnIndex(table))
 812:             target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) 
 813:                                  - getFirstVisibleColumnIndex(table) + 1));
 814:           else
 815:             target = getFirstVisibleColumnIndex(table);
 816:           
 817:           colModel.setLeadSelectionIndex(target);
 818:           rowModel.setLeadSelectionIndex(rowLead);
 819:         }
 820:       else if (command.equals("scrollDownChangeSelection"))
 821:         {
 822:           int target;
 823:           if (rowLead == getLastVisibleRowIndex(table))
 824:             target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table)
 825:                                       - getFirstVisibleRowIndex(table) + 1));
 826:           else
 827:             target = getLastVisibleRowIndex(table);
 828:           
 829:           rowModel.setSelectionInterval(target, target);
 830:           colModel.setSelectionInterval(colLead, colLead);
 831:         }
 832:       else if (command.equals("scrollRightExtendSelection"))
 833:         {
 834:           int target;
 835:           if (colLead == getLastVisibleColumnIndex(table))
 836:             target = Math.min(colMax, colLead + (getLastVisibleColumnIndex(table) 
 837:                 - getFirstVisibleColumnIndex(table) + 1));
 838:           else
 839:             target = getLastVisibleColumnIndex(table);
 840:           
 841:           colModel.setLeadSelectionIndex(target);
 842:           rowModel.setLeadSelectionIndex(rowLead);
 843:         }
 844:       else if (command.equals("selectAll"))
 845:         {
 846:           table.selectAll();
 847:         }
 848:       else if (command.equals("selectLastRowExtendSelection"))
 849:         {
 850:           rowModel.setLeadSelectionIndex(rowMax);
 851:           colModel.setLeadSelectionIndex(colLead);
 852:         }
 853:       else if (command.equals("scrollDownExtendSelection"))
 854:         {
 855:           int target;
 856:           if (rowLead == getLastVisibleRowIndex(table))
 857:             target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table) 
 858:                 - getFirstVisibleRowIndex(table) + 1));
 859:           else
 860:             target = getLastVisibleRowIndex(table);
 861:           
 862:           rowModel.setLeadSelectionIndex(target);
 863:           colModel.setLeadSelectionIndex(colLead);
 864:         }      
 865:       else if (command.equals("scrollUpChangeSelection"))
 866:         {
 867:           int target;
 868:           if (rowLead == getFirstVisibleRowIndex(table))
 869:             target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) 
 870:                 - getFirstVisibleRowIndex(table) + 1));
 871:           else
 872:             target = getFirstVisibleRowIndex(table);
 873:           
 874:           rowModel.setSelectionInterval(target, target);
 875:           colModel.setSelectionInterval(colLead, colLead);
 876:         }
 877:       else if (command.equals("selectNextRowChangeLead"))
 878:           {
 879:             if (rowModel.getSelectionMode() 
 880:                 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
 881:               {
 882:                 // just "selectNextRow"
 883:                 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
 884:                                               Math.min(rowLead + 1, rowMax));
 885:                 colModel.setSelectionInterval(colLead, colLead);
 886:               }
 887:             else
 888:               rowModel.moveLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
 889:           }
 890:       else if (command.equals("selectPreviousRowChangeLead"))
 891:         {
 892:           if (rowModel.getSelectionMode() 
 893:               != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
 894:             {
 895:               // just selectPreviousRow
 896:               rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
 897:                                             Math.min(rowLead - 1, 0));
 898:               colModel.setSelectionInterval(colLead, colLead);
 899:             }
 900:           else
 901:             rowModel.moveLeadSelectionIndex(Math.max(rowLead - 1, 0));
 902:         }
 903:       else if (command.equals("selectNextColumnChangeLead"))
 904:         {
 905:           if (colModel.getSelectionMode() 
 906:               != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)            
 907:             {
 908:               // just selectNextColumn
 909:               rowModel.setSelectionInterval(rowLead, rowLead);
 910:               colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
 911:                                             Math.min(colLead + 1, colMax));
 912:             }
 913:           else
 914:             colModel.moveLeadSelectionIndex(Math.min(colLead + 1, colMax));
 915:         }
 916:       else if (command.equals("selectPreviousColumnChangeLead"))
 917:         {
 918:           if (colModel.getSelectionMode() 
 919:               != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)            
 920:             {
 921:               // just selectPreviousColumn
 922:               rowModel.setSelectionInterval(rowLead, rowLead);
 923:               colModel.setSelectionInterval(Math.max(colLead - 1, 0),
 924:                                             Math.max(colLead - 1, 0));
 925:               
 926:             }
 927:           else
 928:             colModel.moveLeadSelectionIndex(Math.max(colLead - 1, 0));
 929:         }
 930:       else if (command.equals("addToSelection"))
 931:           {
 932:             if (!table.isEditing())
 933:               {
 934:                 int oldRowAnchor = rowModel.getAnchorSelectionIndex();
 935:                 int oldColAnchor = colModel.getAnchorSelectionIndex();
 936:                 rowModel.addSelectionInterval(rowLead, rowLead);
 937:                 colModel.addSelectionInterval(colLead, colLead);
 938:                 rowModel.setAnchorSelectionIndex(oldRowAnchor);
 939:                 colModel.setAnchorSelectionIndex(oldColAnchor);
 940:               }
 941:           }
 942:       else if (command.equals("extendTo"))
 943:         {
 944:           rowModel.setSelectionInterval(rowModel.getAnchorSelectionIndex(),
 945:                                         rowLead);
 946:           colModel.setSelectionInterval(colModel.getAnchorSelectionIndex(),
 947:                                         colLead);
 948:         }
 949:       else if (command.equals("toggleAndAnchor"))
 950:         {
 951:           if (rowModel.isSelectedIndex(rowLead))
 952:             rowModel.removeSelectionInterval(rowLead, rowLead);
 953:           else
 954:             rowModel.addSelectionInterval(rowLead, rowLead);
 955:           
 956:           if (colModel.isSelectedIndex(colLead))
 957:             colModel.removeSelectionInterval(colLead, colLead);
 958:           else
 959:             colModel.addSelectionInterval(colLead, colLead);
 960:           
 961:           rowModel.setAnchorSelectionIndex(rowLead);
 962:           colModel.setAnchorSelectionIndex(colLead);
 963:         }
 964:       else if (command.equals("stopEditing"))
 965:         {
 966:           table.editingStopped(new ChangeEvent(command));
 967:         }
 968:       else 
 969:         {
 970:           // If we're here that means we bound this TableAction class
 971:           // to a keyboard input but we either want to ignore that input
 972:           // or we just haven't implemented its action yet.
 973:           
 974:           // Uncomment the following line to print the names of unused bindings
 975:           // when their keys are pressed
 976:           
 977:           // System.out.println ("not implemented: "+e.getActionCommand());
 978:         }
 979: 
 980:       // Any commands whose keyStrokes should be used by the Editor should not
 981:       // cause editing to be stopped: ie, the SPACE sends "addToSelection" but 
 982:       // if the table is in editing mode, the space should not cause us to stop
 983:       // editing because it should be used by the Editor.
 984:       if (table.isEditing() && command != "startEditing"
 985:           && command != "addToSelection")
 986:         table.editingStopped(new ChangeEvent("update"));
 987:             
 988:       table.scrollRectToVisible(table.getCellRect(
 989:           rowModel.getLeadSelectionIndex(), colModel.getLeadSelectionIndex(), 
 990:           false));
 991:     }
 992:     
 993:     /**
 994:      * Returns the column index of the first visible column.
 995:      * @return the column index of the first visible column.
 996:      */
 997:     int getFirstVisibleColumnIndex(JTable table)
 998:     {
 999:       ComponentOrientation or = table.getComponentOrientation();
1000:       Rectangle r = table.getVisibleRect();
1001:       if (!or.isLeftToRight())
1002:         r.translate((int) r.getWidth() - 1, 0);
1003:       return table.columnAtPoint(r.getLocation());
1004:     }
1005:     
1006:     /**
1007:      * Returns the column index of the last visible column.
1008:      *
1009:      */
1010:     int getLastVisibleColumnIndex(JTable table)
1011:     {
1012:       ComponentOrientation or = table.getComponentOrientation();
1013:       Rectangle r = table.getVisibleRect();
1014:       if (or.isLeftToRight())
1015:         r.translate((int) r.getWidth() - 1, 0);
1016:       return table.columnAtPoint(r.getLocation());      
1017:     }
1018:     
1019:     /**
1020:      * Returns the row index of the first visible row.
1021:      *
1022:      */
1023:     int getFirstVisibleRowIndex(JTable table)
1024:     {
1025:       ComponentOrientation or = table.getComponentOrientation();
1026:       Rectangle r = table.getVisibleRect();
1027:       if (!or.isLeftToRight())
1028:         r.translate((int) r.getWidth() - 1, 0);
1029:       return table.rowAtPoint(r.getLocation());
1030:     }
1031:     
1032:     /**
1033:      * Returns the row index of the last visible row.
1034:      *
1035:      */
1036:     int getLastVisibleRowIndex(JTable table)
1037:     {
1038:       ComponentOrientation or = table.getComponentOrientation();
1039:       Rectangle r = table.getVisibleRect();
1040:       r.translate(0, (int) r.getHeight() - 1);
1041:       if (or.isLeftToRight())
1042:         r.translate((int) r.getWidth() - 1, 0);
1043:       // The next if makes sure that we don't return -1 simply because
1044:       // there is white space at the bottom of the table (ie, the display
1045:       // area is larger than the table)
1046:       if (table.rowAtPoint(r.getLocation()) == -1)
1047:         {
1048:           if (getFirstVisibleRowIndex(table) == -1)
1049:             return -1;
1050:           else
1051:             return table.getModel().getRowCount() - 1;
1052:         }
1053:       return table.rowAtPoint(r.getLocation());
1054:     }
1055: 
1056:     /**
1057:      * A helper method for the key bindings.  Used because the actions
1058:      * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
1059:      *
1060:      * Selects the next (previous if SHIFT pressed) column for TAB, or row for
1061:      * ENTER from within the currently selected cells.
1062:      *
1063:      * @param firstModel the ListSelectionModel for columns (TAB) or
1064:      * rows (ENTER)
1065:      * @param firstMin the first selected index in firstModel
1066:      * @param firstMax the last selected index in firstModel
1067:      * @param secondModel the ListSelectionModel for rows (TAB) or 
1068:      * columns (ENTER)
1069:      * @param secondMin the first selected index in secondModel
1070:      * @param secondMax the last selected index in secondModel
1071:      * @param reverse true if shift was held for the event
1072:      * @param eventIsTab true if TAB was pressed, false if ENTER pressed
1073:      */
1074:     void advanceMultipleSelection(JTable table, ListSelectionModel firstModel,
1075:                                   int firstMin,
1076:                                   int firstMax, ListSelectionModel secondModel, 
1077:                                   int secondMin, int secondMax, boolean reverse,
1078:                                   boolean eventIsTab)
1079:     {
1080:       // If eventIsTab, all the "firsts" correspond to columns, otherwise, to 
1081:       // rows "seconds" correspond to the opposite
1082:       int firstLead = firstModel.getLeadSelectionIndex();
1083:       int secondLead = secondModel.getLeadSelectionIndex();
1084:       int numFirsts = eventIsTab ? 
1085:         table.getModel().getColumnCount() : table.getModel().getRowCount();
1086:       int numSeconds = eventIsTab ? 
1087:         table.getModel().getRowCount() : table.getModel().getColumnCount();
1088: 
1089:       // check if we have to wrap the "firsts" around, going to the other side
1090:       if ((firstLead == firstMax && !reverse) || 
1091:           (reverse && firstLead == firstMin))
1092:         {
1093:           firstModel.addSelectionInterval(reverse ? firstMax : firstMin, 
1094:                                           reverse ? firstMax : firstMin);
1095:           
1096:           // check if we have to wrap the "seconds"
1097:           if ((secondLead == secondMax && !reverse) || 
1098:               (reverse && secondLead == secondMin))
1099:             secondModel.addSelectionInterval(reverse ? secondMax : secondMin, 
1100:                                              reverse ? secondMax : secondMin);
1101: 
1102:           // if we're not wrapping the seconds, we have to find out where we
1103:           // are within the secondModel and advance to the next cell (or 
1104:           // go back to the previous cell if reverse == true)
1105:           else
1106:             {
1107:               int[] secondsSelected;
1108:               if (eventIsTab && table.getRowSelectionAllowed() || 
1109:                   !eventIsTab && table.getColumnSelectionAllowed())
1110:                 secondsSelected = eventIsTab ? 
1111:                   table.getSelectedRows() : table.getSelectedColumns();
1112:               else
1113:                 {
1114:                   // if row selection is not allowed, then the entire column gets
1115:                   // selected when you click on it, so consider ALL rows selected
1116:                   secondsSelected = new int[numSeconds];
1117:                   for (int i = 0; i < numSeconds; i++)
1118:                   secondsSelected[i] = i;
1119:                 }
1120: 
1121:               // and now find the "next" index within the model
1122:               int secondIndex = reverse ? secondsSelected.length - 1 : 0;
1123:               if (!reverse)
1124:                 while (secondsSelected[secondIndex] <= secondLead)
1125:                   secondIndex++;
1126:               else
1127:                 while (secondsSelected[secondIndex] >= secondLead)
1128:                   secondIndex--;
1129:               
1130:               // and select it - updating the lead selection index
1131:               secondModel.addSelectionInterval(secondsSelected[secondIndex], 
1132:                                                secondsSelected[secondIndex]);
1133:             }
1134:         }
1135:       // We didn't have to wrap the firsts, so just find the "next" first
1136:       // and select it, we don't have to change "seconds"
1137:       else
1138:         {
1139:           int[] firstsSelected;
1140:           if (eventIsTab && table.getColumnSelectionAllowed() || 
1141:               !eventIsTab && table.getRowSelectionAllowed())
1142:             firstsSelected = eventIsTab ? 
1143:               table.getSelectedColumns() : table.getSelectedRows();
1144:           else
1145:             {
1146:               // if selection not allowed, consider ALL firsts to be selected
1147:               firstsSelected = new int[numFirsts];
1148:               for (int i = 0; i < numFirsts; i++)
1149:                 firstsSelected[i] = i;
1150:             }
1151:           int firstIndex = reverse ? firstsSelected.length - 1 : 0;
1152:           if (!reverse)
1153:             while (firstsSelected[firstIndex] <= firstLead)
1154:               firstIndex++;
1155:           else 
1156:             while (firstsSelected[firstIndex] >= firstLead)
1157:               firstIndex--;
1158:           firstModel.addSelectionInterval(firstsSelected[firstIndex], 
1159:                                           firstsSelected[firstIndex]);
1160:           secondModel.addSelectionInterval(secondLead, secondLead);
1161:         }
1162:     }
1163:     
1164:     /** 
1165:      * A helper method for the key  bindings. Used because the actions
1166:      * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
1167:      *
1168:      * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER)
1169:      * in the table, changing the current selection.  All cells in the table
1170:      * are eligible, not just the ones that are currently selected.
1171:      * @param firstModel the ListSelectionModel for columns (TAB) or rows
1172:      * (ENTER)
1173:      * @param firstMax the last index in firstModel
1174:      * @param secondModel the ListSelectionModel for rows (TAB) or columns
1175:      * (ENTER)
1176:      * @param secondMax the last index in secondModel
1177:      * @param reverse true if SHIFT was pressed for the event
1178:      */
1179: 
1180:     void advanceSingleSelection(ListSelectionModel firstModel, int firstMax, 
1181:                                 ListSelectionModel secondModel, int secondMax, 
1182:                                 boolean reverse)
1183:     {
1184:       // for TABs, "first" corresponds to columns and "seconds" to rows.
1185:       // the opposite is true for ENTERs
1186:       int firstLead = firstModel.getLeadSelectionIndex();
1187:       int secondLead = secondModel.getLeadSelectionIndex();
1188:       
1189:       // if we are going backwards subtract 2 because we later add 1
1190:       // for a net change of -1
1191:       if (reverse && (firstLead == 0))
1192:         {
1193:           // check if we have to wrap around
1194:           if (secondLead == 0)
1195:             secondLead += secondMax + 1;
1196:           secondLead -= 2;
1197:         }
1198:       
1199:       // do we have to wrap the "seconds"?
1200:       if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax))
1201:         secondModel.setSelectionInterval((secondLead + 1) % (secondMax + 1), 
1202:                                          (secondLead + 1) % (secondMax + 1));
1203:       // if not, just reselect the current lead
1204:       else
1205:         secondModel.setSelectionInterval(secondLead, secondLead);
1206:       
1207:       // if we are going backwards, subtract 2  because we add 1 later
1208:       // for net change of -1
1209:       if (reverse)
1210:         {
1211:           // check for wraparound
1212:           if (firstLead == 0)
1213:             firstLead += firstMax + 1;
1214:           firstLead -= 2;
1215:         }
1216:       // select the next "first"
1217:       firstModel.setSelectionInterval((firstLead + 1) % (firstMax + 1), 
1218:                                       (firstLead + 1) % (firstMax + 1));
1219:     }
1220:   }
1221: 
1222:   protected void installListeners() 
1223:   {
1224:     if (focusListener == null)
1225:       focusListener = createFocusListener();
1226:     table.addFocusListener(focusListener);
1227:     if (keyListener == null)
1228:       keyListener = createKeyListener();
1229:     table.addKeyListener(keyListener);
1230:     if (mouseInputListener == null)
1231:       mouseInputListener = createMouseInputListener();
1232:     table.addMouseListener(mouseInputListener);    
1233:     table.addMouseMotionListener(mouseInputListener);
1234:     if (propertyChangeListener == null)
1235:       propertyChangeListener = new PropertyChangeHandler();
1236:     table.addPropertyChangeListener(propertyChangeListener);
1237:   }
1238: 
1239:   /**
1240:    * Uninstalls UI defaults that have been installed by
1241:    * {@link #installDefaults()}.
1242:    */
1243:   protected void uninstallDefaults()
1244:   {
1245:     // Nothing to do here for now.
1246:   }
1247: 
1248:   /**
1249:    * Uninstalls the keyboard actions that have been installed by
1250:    * {@link #installKeyboardActions()}.
1251:    */
1252:   protected void uninstallKeyboardActions()
1253:   {
1254:     SwingUtilities.replaceUIInputMap(table, JComponent.
1255:                                      WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1256:     SwingUtilities.replaceUIActionMap(table, null);
1257:   }
1258: 
1259:   protected void uninstallListeners() 
1260:   {
1261:     table.removeFocusListener(focusListener);  
1262:     table.removeKeyListener(keyListener);
1263:     table.removeMouseListener(mouseInputListener);    
1264:     table.removeMouseMotionListener(mouseInputListener);
1265:     table.removePropertyChangeListener(propertyChangeListener);
1266:     propertyChangeListener = null;
1267:   }
1268: 
1269:   public void installUI(JComponent comp) 
1270:   {
1271:     table = (JTable) comp;
1272:     rendererPane = new CellRendererPane();
1273:     table.add(rendererPane);
1274: 
1275:     installDefaults();
1276:     installKeyboardActions();
1277:     installListeners();
1278:   }
1279: 
1280:   public void uninstallUI(JComponent c) 
1281:   {
1282:     uninstallListeners();
1283:     uninstallKeyboardActions();
1284:     uninstallDefaults(); 
1285: 
1286:     table.remove(rendererPane);
1287:     rendererPane = null;
1288:     table = null;
1289:   }
1290: 
1291:   /**
1292:    * Paints a single cell in the table.
1293:    *
1294:    * @param g The graphics context to paint in
1295:    * @param row The row number to paint
1296:    * @param col The column number to paint
1297:    * @param bounds The bounds of the cell to paint, assuming a coordinate
1298:    * system beginning at <code>(0,0)</code> in the upper left corner of the
1299:    * table
1300:    * @param rend A cell renderer to paint with
1301:    */
1302:   void paintCell(Graphics g, int row, int col, Rectangle bounds,
1303:                  TableCellRenderer rend)
1304:   {
1305:     Component comp = table.prepareRenderer(rend, row, col);
1306:     rendererPane.paintComponent(g, comp, table, bounds);
1307:   }
1308:   
1309:   /**
1310:    * Paint the associated table.
1311:    */
1312:   public void paint(Graphics gfx, JComponent ignored) 
1313:   {
1314:     int ncols = table.getColumnCount();
1315:     int nrows = table.getRowCount();
1316:     if (nrows == 0 || ncols == 0)
1317:       return;
1318: 
1319:     Rectangle clip = gfx.getClipBounds();
1320: 
1321:     // Determine the range of cells that are within the clip bounds.
1322:     Point p1 = new Point(clip.x, clip.y);
1323:     int c0 = table.columnAtPoint(p1);
1324:     if (c0 == -1)
1325:       c0 = 0;
1326:     int r0 = table.rowAtPoint(p1);
1327:     if (r0 == -1)
1328:       r0 = 0;
1329:     Point p2 = new Point(clip.x + clip.width, clip.y + clip.height);
1330:     int cn = table.columnAtPoint(p2);
1331:     if (cn == -1)
1332:       cn = table.getColumnCount() - 1;
1333:     int rn = table.rowAtPoint(p2);
1334:     if (rn == -1)
1335:       rn = table.getRowCount() - 1;
1336: 
1337:     int columnMargin = table.getColumnModel().getColumnMargin();
1338:     int rowMargin = table.getRowMargin();
1339: 
1340:     TableColumnModel cmodel = table.getColumnModel();
1341:     int[] widths = new int[cn + 1];
1342:     for (int i = c0; i <= cn; i++)
1343:       {
1344:         widths[i] = cmodel.getColumn(i).getWidth() - columnMargin;
1345:       }
1346:     
1347:     Rectangle bounds = table.getCellRect(r0, c0, false);
1348:     // The left boundary of the area being repainted.
1349:     int left = bounds.x;
1350:     
1351:     // The top boundary of the area being repainted.
1352:     int top = bounds.y;
1353:     
1354:     // The bottom boundary of the area being repainted.
1355:     int bottom;
1356:     
1357:     // paint the cell contents
1358:     Color grid = table.getGridColor();    
1359:     for (int r = r0; r <= rn; ++r)
1360:       {
1361:         for (int c = c0; c <= cn; ++c)
1362:           {
1363:             bounds.width = widths[c];
1364:             paintCell(gfx, r, c, bounds, table.getCellRenderer(r, c));
1365:             bounds.x += widths[c] + columnMargin;
1366:           }
1367:         bounds.x = left;
1368:         bounds.y += table.getRowHeight(r);
1369:         // Update row height for tables with custom heights.
1370:         bounds.height = table.getRowHeight(r + 1) - rowMargin;
1371:       }
1372:     
1373:     bottom = bounds.y - rowMargin;
1374: 
1375:     // paint vertical grid lines
1376:     if (grid != null && table.getShowVerticalLines())
1377:       {    
1378:         Color save = gfx.getColor();
1379:         gfx.setColor(grid);
1380:         int x = left - columnMargin;
1381:         for (int c = c0; c <= cn; ++c)
1382:           {
1383:             // The vertical grid is draw right from the cells, so we 
1384:             // add before drawing.
1385:             x += widths[c] + columnMargin;
1386:             gfx.drawLine(x, top, x, bottom);
1387:           }
1388:         gfx.setColor(save);
1389:       }
1390: 
1391:     // paint horizontal grid lines    
1392:     if (grid != null && table.getShowHorizontalLines())
1393:       {    
1394:         Color save = gfx.getColor();
1395:         gfx.setColor(grid);
1396:         int y = top - rowMargin;
1397:         for (int r = r0; r <= rn; ++r)
1398:           {
1399:             // The horizontal grid is draw below the cells, so we 
1400:             // add before drawing.
1401:             y += table.getRowHeight(r);
1402:             gfx.drawLine(left, y, p2.x, y);
1403:           }
1404:         gfx.setColor(save);
1405:       }
1406:   }
1407: }