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.ActionListener;
  50: import java.awt.event.FocusEvent;
  51: import java.awt.event.FocusListener;
  52: import java.awt.event.InputEvent;
  53: import java.awt.event.KeyEvent;
  54: import java.awt.event.KeyListener;
  55: import java.awt.event.MouseEvent;
  56: 
  57: import javax.swing.AbstractAction;
  58: import javax.swing.ActionMap;
  59: import javax.swing.BorderFactory;
  60: import javax.swing.CellRendererPane;
  61: import javax.swing.InputMap;
  62: import javax.swing.JComponent;
  63: import javax.swing.JTable;
  64: import javax.swing.JTextField;
  65: import javax.swing.KeyStroke;
  66: import javax.swing.ListSelectionModel;
  67: import javax.swing.UIDefaults;
  68: import javax.swing.UIManager;
  69: import javax.swing.border.Border;
  70: import javax.swing.event.ChangeEvent;
  71: import javax.swing.event.MouseInputListener;
  72: import javax.swing.plaf.ComponentUI;
  73: import javax.swing.plaf.InputMapUIResource;
  74: import javax.swing.plaf.TableUI;
  75: import javax.swing.table.TableCellRenderer;
  76: import javax.swing.table.TableColumn;
  77: import javax.swing.table.TableColumnModel;
  78: 
  79: public class BasicTableUI
  80:   extends TableUI
  81: {
  82:   public static ComponentUI createUI(JComponent comp) 
  83:   {
  84:     return new BasicTableUI();
  85:   }
  86: 
  87:   protected FocusListener focusListener;  
  88:   protected KeyListener keyListener;   
  89:   protected MouseInputListener    mouseInputListener;   
  90:   protected CellRendererPane rendererPane;   
  91:   protected JTable table;
  92: 
  93:   /** The normal cell border. */
  94:   Border cellBorder;
  95: 
  96:   /** The cell border for selected/highlighted cells. */
  97:   Border highlightCellBorder;
  98: 
  99:   /** The action bound to KeyStrokes. */
 100:   TableAction action;
 101: 
 102:   class FocusHandler implements FocusListener
 103:   {
 104:     public void focusGained(FocusEvent e) 
 105:     {
 106:     }
 107:     public void focusLost(FocusEvent e) 
 108:     {
 109:     }
 110:   }
 111: 
 112:   class MouseInputHandler implements MouseInputListener
 113:   {
 114:     Point begin, curr;
 115: 
 116:     private void updateSelection(boolean controlPressed)
 117:     {
 118:       // Update the rows
 119:       int lo_row = table.rowAtPoint(begin);
 120:       int hi_row  = table.rowAtPoint(curr);
 121:       ListSelectionModel rowModel = table.getSelectionModel();
 122:       if (lo_row != -1 && hi_row != -1)
 123:         {
 124:           if (controlPressed && rowModel.getSelectionMode() 
 125:               != ListSelectionModel.SINGLE_SELECTION)
 126:             rowModel.addSelectionInterval(lo_row, hi_row);
 127:           else
 128:             rowModel.setSelectionInterval(lo_row, hi_row);
 129:         }
 130:       
 131:       // Update the columns
 132:       int lo_col = table.columnAtPoint(begin);
 133:       int hi_col = table.columnAtPoint(curr);
 134:       ListSelectionModel colModel = table.getColumnModel().
 135:         getSelectionModel();
 136:       if (lo_col != -1 && hi_col != -1)
 137:         {
 138:           if (controlPressed && colModel.getSelectionMode() != 
 139:               ListSelectionModel.SINGLE_SELECTION)
 140:             colModel.addSelectionInterval(lo_col, hi_col);
 141:           else
 142:             colModel.setSelectionInterval(lo_col, hi_col);
 143:         }
 144:     }
 145: 
 146:     public void mouseClicked(MouseEvent e) 
 147:     {
 148:     }
 149:     public void mouseDragged(MouseEvent e) 
 150:     {
 151:       curr = new Point(e.getX(), e.getY());
 152:       updateSelection(e.isControlDown());      
 153:     }
 154:     public void mouseEntered(MouseEvent e) 
 155:     {
 156:     }
 157:     public void mouseExited(MouseEvent e) 
 158:     {
 159:     }
 160:     public void mouseMoved(MouseEvent e) 
 161:     {
 162:     }
 163:     public void mousePressed(MouseEvent e) 
 164:     {
 165:       ListSelectionModel rowModel = table.getSelectionModel();
 166:       ListSelectionModel colModel = table.getColumnModel().getSelectionModel();
 167:       int rowLead = rowModel.getLeadSelectionIndex();
 168:       int colLead = colModel.getLeadSelectionIndex();
 169: 
 170:       begin = new Point(e.getX(), e.getY());
 171:       curr = new Point(e.getX(), e.getY());
 172:       //if control is pressed and the cell is already selected, deselect it
 173:       if (e.isControlDown() && table.
 174:           isCellSelected(table.rowAtPoint(begin),table.columnAtPoint(begin)))
 175:         {                                       
 176:           table.getSelectionModel().
 177:             removeSelectionInterval(table.rowAtPoint(begin), 
 178:                                     table.rowAtPoint(begin));
 179:           table.getColumnModel().getSelectionModel().
 180:             removeSelectionInterval(table.columnAtPoint(begin), 
 181:                                     table.columnAtPoint(begin));
 182:         }
 183:       else
 184:         updateSelection(e.isControlDown());
 185: 
 186:       // If we were editing, but the moved to another cell, stop editing
 187:       if (rowLead != rowModel.getLeadSelectionIndex() ||
 188:           colLead != colModel.getLeadSelectionIndex())
 189:         if (table.isEditing())
 190:           table.editingStopped(new ChangeEvent(e));
 191:     }
 192:     public void mouseReleased(MouseEvent e) 
 193:     {
 194:       begin = null;
 195:       curr = null;
 196:     }
 197:   }
 198: 
 199:   protected FocusListener createFocusListener() 
 200:   {
 201:     return new FocusHandler();
 202:   }
 203: 
 204:   protected MouseInputListener createMouseInputListener() 
 205:   {
 206:     return new MouseInputHandler();
 207:   }
 208: 
 209:   /**
 210:    * Return the maximum size of the table. The maximum height is the row 
 211:     * height times the number of rows. The maximum width is the sum of 
 212:     * the maximum widths of each column.
 213:     * 
 214:     *  @param comp the component whose maximum size is being queried,
 215:     *  this is ignored.
 216:     *  @return a Dimension object representing the maximum size of the table,
 217:     *  or null if the table has no elements.
 218:    */
 219:   public Dimension getMaximumSize(JComponent comp) 
 220:   {
 221:     int maxTotalColumnWidth = 0;
 222:     for (int i = 0; i < table.getColumnCount(); i++)
 223:       maxTotalColumnWidth += table.getColumnModel().getColumn(i).getMaxWidth();
 224:     if (maxTotalColumnWidth == 0 || table.getRowCount() == 0)
 225:       return null;
 226:     return new Dimension(maxTotalColumnWidth, table.getRowCount()*table.getRowHeight());
 227:   }
 228: 
 229:   /**
 230:    * Return the minimum size of the table. The minimum height is the row 
 231:     * height times the number of rows. The minimum width is the sum of 
 232:     * the minimum widths of each column.
 233:     * 
 234:     *  @param comp the component whose minimum size is being queried,
 235:     *  this is ignored.
 236:     *  @return a Dimension object representing the minimum size of the table,
 237:     *  or null if the table has no elements.
 238:    */
 239:   public Dimension getMinimumSize(JComponent comp) 
 240:   {
 241:     int minTotalColumnWidth = 0;
 242:     for (int i = 0; i < table.getColumnCount(); i++)
 243:       minTotalColumnWidth += table.getColumnModel().getColumn(i).getMinWidth();
 244:     if (minTotalColumnWidth == 0 || table.getRowCount() == 0)
 245:       return null;
 246:     return new Dimension(minTotalColumnWidth, table.getRowCount()*table.getRowHeight());
 247:   }
 248: 
 249:   public Dimension getPreferredSize(JComponent comp) 
 250:   {
 251:     int width = table.getColumnModel().getTotalColumnWidth();
 252:     int height = table.getRowCount() * table.getRowHeight();
 253:     return new Dimension(width, height);
 254:   }
 255: 
 256:   protected void installDefaults() 
 257:   {
 258:     UIDefaults defaults = UIManager.getLookAndFeelDefaults();
 259:     table.setFont(defaults.getFont("Table.font"));
 260:     table.setGridColor(defaults.getColor("Table.gridColor"));
 261:     table.setForeground(defaults.getColor("Table.foreground"));
 262:     table.setBackground(defaults.getColor("Table.background"));
 263:     table.setSelectionForeground(defaults.getColor("Table.selectionForeground"));
 264:     table.setSelectionBackground(defaults.getColor("Table.selectionBackground"));
 265:     table.setOpaque(true);
 266: 
 267:     highlightCellBorder = defaults.getBorder("Table.focusCellHighlightBorder");
 268:     cellBorder = BorderFactory.createEmptyBorder(1, 1, 1, 1);
 269:   }
 270: 
 271:   private int convertModifiers(int mod)
 272:   {
 273:     if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0)
 274:       {
 275:         mod |= KeyEvent.SHIFT_MASK;
 276:         mod &= ~KeyEvent.SHIFT_DOWN_MASK;
 277:       }
 278:     if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0)
 279:       {
 280:         mod |= KeyEvent.CTRL_MASK;
 281:         mod &= ~KeyEvent.CTRL_DOWN_MASK;
 282:       }
 283:     if ((mod & KeyEvent.META_DOWN_MASK) != 0)
 284:       {
 285:         mod |= KeyEvent.META_MASK;
 286:         mod &= ~KeyEvent.META_DOWN_MASK;
 287:       }
 288:     if ((mod & KeyEvent.ALT_DOWN_MASK) != 0)
 289:       {
 290:         mod |= KeyEvent.ALT_MASK;
 291:         mod &= ~KeyEvent.ALT_DOWN_MASK;
 292:       }
 293:     if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0)
 294:       {
 295:         mod |= KeyEvent.ALT_GRAPH_MASK;
 296:         mod &= ~KeyEvent.ALT_GRAPH_DOWN_MASK;
 297:       }
 298:     return mod;
 299:   }
 300: 
 301:   protected void installKeyboardActions() 
 302:   {
 303:     UIDefaults defaults = UIManager.getLookAndFeelDefaults();
 304:     InputMap ancestorMap = (InputMap)defaults.get("Table.ancestorInputMap");
 305:     InputMapUIResource parentInputMap = new InputMapUIResource();
 306:     // FIXME: The JDK uses a LazyActionMap for parentActionMap
 307:     ActionMap parentActionMap = new ActionMap();
 308:     action = new TableAction();
 309:     Object keys[] = ancestorMap.allKeys();
 310:     // Register key bindings in the UI InputMap-ActionMap pair
 311:     // Note that we register key bindings with both the old and new modifier
 312:     // masks: InputEvent.SHIFT_MASK and InputEvent.SHIFT_DOWN_MASK and so on.
 313:     for (int i = 0; i < keys.length; i++)
 314:       {
 315:         parentInputMap.put(KeyStroke.getKeyStroke
 316:                       (((KeyStroke)keys[i]).getKeyCode(), convertModifiers
 317:                        (((KeyStroke)keys[i]).getModifiers())),
 318:                            (String)ancestorMap.get((KeyStroke)keys[i]));
 319: 
 320:         parentInputMap.put(KeyStroke.getKeyStroke
 321:                       (((KeyStroke)keys[i]).getKeyCode(), 
 322:                        ((KeyStroke)keys[i]).getModifiers()),
 323:                            (String)ancestorMap.get((KeyStroke)keys[i]));
 324: 
 325:         parentActionMap.put
 326:           ((String)ancestorMap.get((KeyStroke)keys[i]), new ActionListenerProxy
 327:            (action, (String)ancestorMap.get((KeyStroke)keys[i])));
 328: 
 329:       }
 330:     // Set the UI InputMap-ActionMap pair to be the parents of the
 331:     // JTable's InputMap-ActionMap pair
 332:     parentInputMap.setParent
 333:       (table.getInputMap
 334:        (JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent());
 335:     parentActionMap.setParent(table.getActionMap().getParent());
 336:     table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
 337:       setParent(parentInputMap);
 338:     table.getActionMap().setParent(parentActionMap);
 339:   }
 340: 
 341:   /**
 342:    * This class is used to mimmic the behaviour of the JDK when registering
 343:    * keyboard actions.  It is the same as the private class used in JComponent
 344:    * for the same reason.  This class receives an action event and dispatches
 345:    * it to the true receiver after altering the actionCommand property of the
 346:    * event.
 347:    */
 348:   private static class ActionListenerProxy
 349:     extends AbstractAction
 350:   {
 351:     ActionListener target;
 352:     String bindingCommandName;
 353: 
 354:     public ActionListenerProxy(ActionListener li, 
 355:                                String cmd)
 356:     {
 357:       target = li;
 358:       bindingCommandName = cmd;
 359:     }
 360: 
 361:     public void actionPerformed(ActionEvent e)
 362:     {
 363:       ActionEvent derivedEvent = new ActionEvent(e.getSource(),
 364:                                                  e.getID(),
 365:                                                  bindingCommandName,
 366:                                                  e.getModifiers());
 367:       target.actionPerformed(derivedEvent);
 368:     }
 369:   }
 370: 
 371:   /**
 372:    * This class implements the actions that we want to happen
 373:    * when specific keys are pressed for the JTable.  The actionPerformed
 374:    * method is called when a key that has been registered for the JTable
 375:    * is received.
 376:    */
 377:   class TableAction extends AbstractAction
 378:   {
 379:     /**
 380:      * What to do when this action is called.
 381:      *
 382:      * @param e the ActionEvent that caused this action.
 383:      */
 384:     public void actionPerformed (ActionEvent e)
 385:     {
 386:       ListSelectionModel rowModel = table.getSelectionModel();
 387:       ListSelectionModel colModel = table.getColumnModel().getSelectionModel();
 388: 
 389:       int rowLead = rowModel.getLeadSelectionIndex();
 390:       int rowMax = table.getModel().getRowCount() - 1;
 391:       
 392:       int colLead = colModel.getLeadSelectionIndex();
 393:       int colMax = table.getModel().getColumnCount() - 1;
 394:       
 395:       if (e.getActionCommand().equals("selectPreviousRowExtendSelection"))
 396:         {
 397:           rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0));
 398:           colModel.setLeadSelectionIndex(colLead);
 399:         }
 400:       else if (e.getActionCommand().equals("selectLastColumn"))
 401:         {
 402:           table.clearSelection();
 403:           rowModel.setSelectionInterval(rowLead, rowLead);
 404:           colModel.setSelectionInterval(colMax, colMax);
 405:         }
 406:       else if (e.getActionCommand().equals("startEditing"))
 407:         {
 408:           if (table.isCellEditable(rowLead, colLead))
 409:             table.editCellAt(rowLead,colLead);
 410:         }
 411:       else if (e.getActionCommand().equals("selectFirstRowExtendSelection"))
 412:         {              
 413:           rowModel.setLeadSelectionIndex(0);
 414:           colModel.setLeadSelectionIndex(colLead);
 415:         }
 416:       else if (e.getActionCommand().equals("selectFirstColumn"))
 417:         {
 418:           rowModel.setSelectionInterval(rowLead, rowLead);
 419:           colModel.setSelectionInterval(0, 0);
 420:         }
 421:       else if (e.getActionCommand().equals("selectFirstColumnExtendSelection"))
 422:         {
 423:           colModel.setLeadSelectionIndex(0);
 424:           rowModel.setLeadSelectionIndex(rowLead);
 425:         }
 426:       else if (e.getActionCommand().equals("selectLastRow"))
 427:         {
 428:           rowModel.setSelectionInterval(rowMax,rowMax);
 429:           colModel.setSelectionInterval(colLead, colLead);
 430:         }
 431:       else if (e.getActionCommand().equals("selectNextRowExtendSelection"))
 432:         {
 433:           rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
 434:           colModel.setLeadSelectionIndex(colLead);
 435:         }
 436:       else if (e.getActionCommand().equals("selectFirstRow"))
 437:         {
 438:           rowModel.setSelectionInterval(0,0);
 439:           colModel.setSelectionInterval(colLead, colLead);
 440:         }
 441:       else if (e.getActionCommand().equals("selectNextColumnExtendSelection"))
 442:         {
 443:           colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax));
 444:           rowModel.setLeadSelectionIndex(rowLead);
 445:         }
 446:       else if (e.getActionCommand().equals("selectLastColumnExtendSelection"))
 447:         {
 448:           colModel.setLeadSelectionIndex(colMax);
 449:           rowModel.setLeadSelectionIndex(rowLead);
 450:         }
 451:       else if (e.getActionCommand().equals("selectPreviousColumnExtendSelection"))
 452:         {
 453:           colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0));
 454:           rowModel.setLeadSelectionIndex(rowLead);
 455:         }
 456:       else if (e.getActionCommand().equals("selectNextRow"))
 457:         {
 458:           rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
 459:                                         Math.min(rowLead + 1, rowMax));
 460:           colModel.setSelectionInterval(colLead,colLead);
 461:         }
 462:       else if (e.getActionCommand().equals("scrollUpExtendSelection"))
 463:         {
 464:           int target;
 465:           if (rowLead == getFirstVisibleRowIndex())
 466:             target = Math.max
 467:               (0, rowLead - (getLastVisibleRowIndex() - 
 468:                              getFirstVisibleRowIndex() + 1));
 469:           else
 470:             target = getFirstVisibleRowIndex();
 471:           
 472:           rowModel.setLeadSelectionIndex(target);
 473:           colModel.setLeadSelectionIndex(colLead);
 474:         }
 475:       else if (e.getActionCommand().equals("selectPreviousRow"))
 476:         {
 477:           rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
 478:                                         Math.max(rowLead - 1, 0));
 479:           colModel.setSelectionInterval(colLead,colLead);
 480:         }
 481:       else if (e.getActionCommand().equals("scrollRightChangeSelection"))
 482:         {
 483:           int target;
 484:           if (colLead == getLastVisibleColumnIndex())
 485:             target = Math.min
 486:               (colMax, colLead + (getLastVisibleColumnIndex() -
 487:                                   getFirstVisibleColumnIndex() + 1));
 488:           else
 489:             target = getLastVisibleColumnIndex();
 490:           
 491:           colModel.setSelectionInterval(target, target);
 492:           rowModel.setSelectionInterval(rowLead, rowLead);
 493:         }
 494:       else if (e.getActionCommand().equals("selectPreviousColumn"))
 495:         {
 496:           rowModel.setSelectionInterval(rowLead,rowLead);
 497:           colModel.setSelectionInterval(Math.max(colLead - 1, 0),
 498:                                         Math.max(colLead - 1, 0));
 499:         }
 500:       else if (e.getActionCommand().equals("scrollLeftChangeSelection"))
 501:         {
 502:           int target;
 503:           if (colLead == getFirstVisibleColumnIndex())
 504:             target = Math.max
 505:               (0, colLead - (getLastVisibleColumnIndex() -
 506:                              getFirstVisibleColumnIndex() + 1));
 507:           else
 508:             target = getFirstVisibleColumnIndex();
 509:           
 510:           colModel.setSelectionInterval(target, target);
 511:           rowModel.setSelectionInterval(rowLead, rowLead);
 512:         }
 513:       else if (e.getActionCommand().equals("clearSelection"))
 514:         {
 515:           table.clearSelection();
 516:         }
 517:       else if (e.getActionCommand().equals("cancel"))
 518:         {
 519:           // FIXME: implement other parts of "cancel" like undo-ing last
 520:           // selection.  Right now it just calls editingCancelled if
 521:           // we're currently editing.
 522:           if (table.isEditing())
 523:             table.editingCanceled(new ChangeEvent("cancel"));
 524:         }
 525:       else if (e.getActionCommand().equals("selectNextRowCell")
 526:                || e.getActionCommand().equals("selectPreviousRowCell")
 527:                || e.getActionCommand().equals("selectNextColumnCell")
 528:                || e.getActionCommand().equals("selectPreviousColumnCell"))
 529:         {
 530:           // If nothing is selected, select the first cell in the table
 531:           if (table.getSelectedRowCount() == 0 && 
 532:               table.getSelectedColumnCount() == 0)
 533:             {
 534:               rowModel.setSelectionInterval(0, 0);
 535:               colModel.setSelectionInterval(0, 0);
 536:               return;
 537:             }
 538:           
 539:           // If the lead selection index isn't selected (ie a remove operation
 540:           // happened, then set the lead to the first selected cell in the
 541:           // table
 542:           if (!table.isCellSelected(rowLead, colLead))
 543:             {
 544:               rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(), 
 545:                                             rowModel.getMinSelectionIndex());
 546:               colModel.addSelectionInterval(colModel.getMinSelectionIndex(), 
 547:                                             colModel.getMinSelectionIndex());
 548:               return;
 549:             }
 550:           
 551:           // multRowsSelected and multColsSelected tell us if multiple rows or
 552:           // columns are selected, respectively
 553:           boolean multRowsSelected, multColsSelected;
 554:           multRowsSelected = table.getSelectedRowCount() > 1 &&
 555:             table.getRowSelectionAllowed();
 556:           
 557:           multColsSelected = table.getSelectedColumnCount() > 1 &&
 558:             table.getColumnSelectionAllowed();
 559:           
 560:           // If there is just one selection, select the next cell, and wrap
 561:           // when you get to the edges of the table.
 562:           if (!multColsSelected && !multRowsSelected)
 563:             {
 564:               if (e.getActionCommand().indexOf("Column") != -1) 
 565:                 advanceSingleSelection(colModel, colMax, rowModel, rowMax, 
 566:                                        (e.getActionCommand().equals
 567:                                         ("selectPreviousColumnCell")));
 568:               else
 569:                 advanceSingleSelection(rowModel, rowMax, colModel, colMax, 
 570:                                        (e.getActionCommand().equals 
 571:                                         ("selectPreviousRowCell")));
 572:               return;
 573:             }
 574:           
 575:           
 576:           // rowMinSelected and rowMaxSelected are the minimum and maximum
 577:           // values respectively of selected cells in the row selection model
 578:           // Similarly for colMinSelected and colMaxSelected.
 579:           int rowMaxSelected = table.getRowSelectionAllowed() ? 
 580:             rowModel.getMaxSelectionIndex() : table.getModel().getRowCount() - 1;
 581:           int rowMinSelected = table.getRowSelectionAllowed() ? 
 582:             rowModel.getMinSelectionIndex() : 0; 
 583:           int colMaxSelected = table.getColumnSelectionAllowed() ? 
 584:             colModel.getMaxSelectionIndex() : 
 585:             table.getModel().getColumnCount() - 1;
 586:           int colMinSelected = table.getColumnSelectionAllowed() ? 
 587:             colModel.getMinSelectionIndex() : 0;
 588:           
 589:           // If there are multiple rows and columns selected, select the next
 590:           // cell and wrap at the edges of the selection.  
 591:           if (e.getActionCommand().indexOf("Column") != -1) 
 592:             advanceMultipleSelection(colModel, colMinSelected, colMaxSelected, 
 593:                                      rowModel, rowMinSelected, rowMaxSelected, 
 594:                                      (e.getActionCommand().equals
 595:                                       ("selectPreviousColumnCell")), true);
 596:           
 597:           else
 598:             advanceMultipleSelection(rowModel, rowMinSelected, rowMaxSelected, 
 599:                                      colModel, colMinSelected, colMaxSelected, 
 600:                                      (e.getActionCommand().equals 
 601:                                       ("selectPreviousRowCell")), false);
 602:         }
 603:       else if (e.getActionCommand().equals("selectNextColumn"))
 604:         {
 605:           rowModel.setSelectionInterval(rowLead,rowLead);
 606:           colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
 607:                                         Math.min(colLead + 1, colMax));
 608:         }
 609:       else if (e.getActionCommand().equals("scrollLeftExtendSelection"))
 610:         {
 611:           int target;
 612:           if (colLead == getFirstVisibleColumnIndex())
 613:             target = Math.max
 614:               (0, colLead - (getLastVisibleColumnIndex() -
 615:                              getFirstVisibleColumnIndex() + 1));
 616:           else
 617:             target = getFirstVisibleColumnIndex();
 618:           
 619:           colModel.setLeadSelectionIndex(target);
 620:           rowModel.setLeadSelectionIndex(rowLead);
 621:         }
 622:       else if (e.getActionCommand().equals("scrollDownChangeSelection"))
 623:         {
 624:           int target;
 625:           if (rowLead == getLastVisibleRowIndex())
 626:             target = Math.min
 627:               (rowMax, rowLead + (getLastVisibleRowIndex() - 
 628:                                   getFirstVisibleRowIndex() + 1));
 629:           else
 630:             target = getLastVisibleRowIndex();
 631:           
 632:           rowModel.setSelectionInterval(target, target);
 633:           colModel.setSelectionInterval(colLead, colLead);
 634:         }
 635:       else if (e.getActionCommand().equals("scrollRightExtendSelection"))
 636:         {
 637:           int target;
 638:           if (colLead == getLastVisibleColumnIndex())
 639:             target = Math.min
 640:               (colMax, colLead + (getLastVisibleColumnIndex() -
 641:                                   getFirstVisibleColumnIndex() + 1));
 642:           else
 643:             target = getLastVisibleColumnIndex();
 644:           
 645:           colModel.setLeadSelectionIndex(target);
 646:           rowModel.setLeadSelectionIndex(rowLead);
 647:         }
 648:       else if (e.getActionCommand().equals("selectAll"))
 649:         {
 650:           table.selectAll();
 651:         }
 652:       else if (e.getActionCommand().equals("selectLastRowExtendSelection"))
 653:         {
 654:           rowModel.setLeadSelectionIndex(rowMax);
 655:           colModel.setLeadSelectionIndex(colLead);
 656:         }
 657:       else if (e.getActionCommand().equals("scrollDownExtendSelection"))
 658:         {
 659:           int target;
 660:           if (rowLead == getLastVisibleRowIndex())
 661:             target = Math.min
 662:               (rowMax, rowLead + (getLastVisibleRowIndex() - 
 663:                                   getFirstVisibleRowIndex() + 1));
 664:           else
 665:             target = getLastVisibleRowIndex();
 666:           
 667:           rowModel.setLeadSelectionIndex(target);
 668:           colModel.setLeadSelectionIndex(colLead);
 669:         }
 670:       else if (e.getActionCommand().equals("scrollUpChangeSelection"))
 671:         {
 672:           int target;
 673:           if (rowLead == getFirstVisibleRowIndex())
 674:             target = Math.max
 675:               (0, rowLead - (getLastVisibleRowIndex() - 
 676:                              getFirstVisibleRowIndex() + 1));
 677:           else
 678:             target = getFirstVisibleRowIndex();
 679:           
 680:           rowModel.setSelectionInterval(target, target);
 681:           colModel.setSelectionInterval(colLead, colLead);
 682:         }
 683:       else 
 684:         {
 685:           // If we're here that means we bound this TableAction class
 686:           // to a keyboard input but we either want to ignore that input
 687:           // or we just haven't implemented its action yet.
 688:         }
 689: 
 690:       if (table.isEditing() && e.getActionCommand() != "startEditing")
 691:         table.editingCanceled(new ChangeEvent("update"));
 692:       table.repaint();
 693:       
 694:       table.scrollRectToVisible
 695:         (table.getCellRect(rowModel.getLeadSelectionIndex(), 
 696:                            colModel.getLeadSelectionIndex(), false));
 697:     }
 698:     
 699:     int getFirstVisibleColumnIndex()
 700:     {
 701:       ComponentOrientation or = table.getComponentOrientation();
 702:       Rectangle r = table.getVisibleRect();
 703:       if (!or.isLeftToRight())
 704:         r.translate((int) r.getWidth() - 1, 0);
 705:       return table.columnAtPoint(r.getLocation());
 706:     }
 707:     
 708:     /**
 709:      * Returns the column index of the last visible column.
 710:      *
 711:      */
 712:     int getLastVisibleColumnIndex()
 713:     {
 714:       ComponentOrientation or = table.getComponentOrientation();
 715:       Rectangle r = table.getVisibleRect();
 716:       if (or.isLeftToRight())
 717:         r.translate((int) r.getWidth() - 1, 0);
 718:       return table.columnAtPoint(r.getLocation());      
 719:     }
 720:     
 721:     /**
 722:      * Returns the row index of the first visible row.
 723:      *
 724:      */
 725:     int getFirstVisibleRowIndex()
 726:     {
 727:       ComponentOrientation or = table.getComponentOrientation();
 728:       Rectangle r = table.getVisibleRect();
 729:       if (!or.isLeftToRight())
 730:         r.translate((int) r.getWidth() - 1, 0);
 731:       return table.rowAtPoint(r.getLocation());
 732:     }
 733:     
 734:     /**
 735:      * Returns the row index of the last visible row.
 736:      *
 737:      */
 738:     int getLastVisibleRowIndex()
 739:     {
 740:       ComponentOrientation or = table.getComponentOrientation();
 741:       Rectangle r = table.getVisibleRect();
 742:       r.translate(0, (int) r.getHeight() - 1);
 743:       if (or.isLeftToRight())
 744:         r.translate((int) r.getWidth() - 1, 0);
 745:       // The next if makes sure that we don't return -1 simply because
 746:       // there is white space at the bottom of the table (ie, the display
 747:       // area is larger than the table)
 748:       if (table.rowAtPoint(r.getLocation()) == -1)
 749:         {
 750:           if (getFirstVisibleRowIndex() == -1)
 751:             return -1;
 752:           else
 753:             return table.getModel().getRowCount() - 1;
 754:         }
 755:       return table.rowAtPoint(r.getLocation());
 756:     }
 757: 
 758:     /**
 759:      * A helper method for the key bindings.  Used because the actions
 760:      * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
 761:      *
 762:      * Selects the next (previous if SHIFT pressed) column for TAB, or row for
 763:      * ENTER from within the currently selected cells.
 764:      *
 765:      * @param firstModel the ListSelectionModel for columns (TAB) or
 766:      * rows (ENTER)
 767:      * @param firstMin the first selected index in firstModel
 768:      * @param firstMax the last selected index in firstModel
 769:      * @param secondModel the ListSelectionModel for rows (TAB) or 
 770:      * columns (ENTER)
 771:      * @param secondMin the first selected index in secondModel
 772:      * @param secondMax the last selected index in secondModel
 773:      * @param reverse true if shift was held for the event
 774:      * @param eventIsTab true if TAB was pressed, false if ENTER pressed
 775:      */
 776:     void advanceMultipleSelection (ListSelectionModel firstModel, int firstMin,
 777:                                    int firstMax, ListSelectionModel secondModel, 
 778:                                    int secondMin, int secondMax, boolean reverse,
 779:                                    boolean eventIsTab)
 780:     {
 781:       // If eventIsTab, all the "firsts" correspond to columns, otherwise, to rows
 782:       // "seconds" correspond to the opposite
 783:       int firstLead = firstModel.getLeadSelectionIndex();
 784:       int secondLead = secondModel.getLeadSelectionIndex();
 785:       int numFirsts = eventIsTab ? 
 786:         table.getModel().getColumnCount() : table.getModel().getRowCount();
 787:       int numSeconds = eventIsTab ? 
 788:         table.getModel().getRowCount() : table.getModel().getColumnCount();
 789: 
 790:       // check if we have to wrap the "firsts" around, going to the other side
 791:       if ((firstLead == firstMax && !reverse) || 
 792:           (reverse && firstLead == firstMin))
 793:         {
 794:           firstModel.addSelectionInterval(reverse ? firstMax : firstMin, 
 795:                                           reverse ? firstMax : firstMin);
 796:           
 797:           // check if we have to wrap the "seconds"
 798:           if ((secondLead == secondMax && !reverse) || 
 799:               (reverse && secondLead == secondMin))
 800:             secondModel.addSelectionInterval(reverse ? secondMax : secondMin, 
 801:                                              reverse ? secondMax : secondMin);
 802: 
 803:           // if we're not wrapping the seconds, we have to find out where we
 804:           // are within the secondModel and advance to the next cell (or 
 805:           // go back to the previous cell if reverse == true)
 806:           else
 807:             {
 808:               int[] secondsSelected;
 809:               if (eventIsTab && table.getRowSelectionAllowed() || 
 810:                   !eventIsTab && table.getColumnSelectionAllowed())
 811:                 secondsSelected = eventIsTab ? 
 812:                   table.getSelectedRows() : table.getSelectedColumns();
 813:               else
 814:                 {
 815:                   // if row selection is not allowed, then the entire column gets
 816:                   // selected when you click on it, so consider ALL rows selected
 817:                   secondsSelected = new int[numSeconds];
 818:                   for (int i = 0; i < numSeconds; i++)
 819:                   secondsSelected[i] = i;
 820:                 }
 821: 
 822:               // and now find the "next" index within the model
 823:               int secondIndex = reverse ? secondsSelected.length - 1 : 0;
 824:               if (!reverse)
 825:                 while (secondsSelected[secondIndex] <= secondLead)
 826:                   secondIndex++;
 827:               else
 828:                 while (secondsSelected[secondIndex] >= secondLead)
 829:                   secondIndex--;
 830:               
 831:               // and select it - updating the lead selection index
 832:               secondModel.addSelectionInterval(secondsSelected[secondIndex], 
 833:                                                secondsSelected[secondIndex]);
 834:             }
 835:         }
 836:       // We didn't have to wrap the firsts, so just find the "next" first
 837:       // and select it, we don't have to change "seconds"
 838:       else
 839:         {
 840:           int[] firstsSelected;
 841:           if (eventIsTab && table.getColumnSelectionAllowed() || 
 842:               !eventIsTab && table.getRowSelectionAllowed())
 843:             firstsSelected = eventIsTab ? 
 844:               table.getSelectedColumns() : table.getSelectedRows();
 845:           else
 846:             {
 847:               // if selection not allowed, consider ALL firsts to be selected
 848:               firstsSelected = new int[numFirsts];
 849:               for (int i = 0; i < numFirsts; i++)
 850:                 firstsSelected[i] = i;
 851:             }
 852:           int firstIndex = reverse ? firstsSelected.length - 1 : 0;
 853:           if (!reverse)
 854:             while (firstsSelected[firstIndex] <= firstLead)
 855:               firstIndex++;
 856:           else 
 857:             while (firstsSelected[firstIndex] >= firstLead)
 858:               firstIndex--;
 859:           firstModel.addSelectionInterval(firstsSelected[firstIndex], 
 860:                                           firstsSelected[firstIndex]);
 861:           secondModel.addSelectionInterval(secondLead, secondLead);
 862:         }
 863:     }
 864:     
 865:     /** 
 866:      * A helper method for the key  bindings. Used because the actions
 867:      * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
 868:      *
 869:      * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER)
 870:      * in the table, changing the current selection.  All cells in the table
 871:      * are eligible, not just the ones that are currently selected.
 872:      * @param firstModel the ListSelectionModel for columns (TAB) or rows
 873:      * (ENTER)
 874:      * @param firstMax the last index in firstModel
 875:      * @param secondModel the ListSelectionModel for rows (TAB) or columns
 876:      * (ENTER)
 877:      * @param secondMax the last index in secondModel
 878:      * @param reverse true if SHIFT was pressed for the event
 879:      */
 880: 
 881:     void advanceSingleSelection (ListSelectionModel firstModel, int firstMax, 
 882:                                  ListSelectionModel secondModel, int secondMax, 
 883:                                  boolean reverse)
 884:     {
 885:       // for TABs, "first" corresponds to columns and "seconds" to rows.
 886:       // the opposite is true for ENTERs
 887:       int firstLead = firstModel.getLeadSelectionIndex();
 888:       int secondLead = secondModel.getLeadSelectionIndex();
 889:       
 890:       // if we are going backwards subtract 2 because we later add 1
 891:       // for a net change of -1
 892:       if (reverse && (firstLead == 0))
 893:         {
 894:           // check if we have to wrap around
 895:           if (secondLead == 0)
 896:             secondLead += secondMax + 1;
 897:           secondLead -= 2;
 898:         }
 899:       
 900:       // do we have to wrap the "seconds"?
 901:       if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax))
 902:         secondModel.setSelectionInterval((secondLead + 1)%(secondMax + 1), 
 903:                                          (secondLead + 1)%(secondMax + 1));
 904:       // if not, just reselect the current lead
 905:       else
 906:         secondModel.setSelectionInterval(secondLead, secondLead);
 907:       
 908:       // if we are going backwards, subtract 2  because we add 1 later
 909:       // for net change of -1
 910:       if (reverse)
 911:         {
 912:           // check for wraparound
 913:           if (firstLead == 0)
 914:             firstLead += firstMax + 1;
 915:           firstLead -= 2;
 916:         }
 917:       // select the next "first"
 918:       firstModel.setSelectionInterval ((firstLead + 1)%(firstMax + 1), 
 919:                                        (firstLead + 1)%(firstMax + 1));
 920:     }
 921:   }
 922: 
 923:   protected void installListeners() 
 924:   {
 925:     table.addFocusListener(focusListener);  
 926:     table.addKeyListener(keyListener);
 927:     table.addMouseListener(mouseInputListener);    
 928:     table.addMouseMotionListener(mouseInputListener);
 929:   }
 930: 
 931:   protected void uninstallDefaults() 
 932:   {
 933:     // TODO: this method used to do the following which is not
 934:     // quite right (at least it breaks apps that run fine with the
 935:     // JDK):
 936:     //
 937:     // table.setFont(null);
 938:     // table.setGridColor(null);
 939:     // table.setForeground(null);
 940:     // table.setBackground(null);
 941:     // table.setSelectionForeground(null);
 942:     // table.setSelectionBackground(null);
 943:     //
 944:     // This would leave the component in a corrupt state, which is
 945:     // not acceptable. A possible solution would be to have component
 946:     // level defaults installed, that get overridden by the UI defaults
 947:     // and get restored in this method. I am not quite sure about this
 948:     // though. / Roman Kennke
 949:   }
 950: 
 951:   protected void uninstallKeyboardActions() 
 952:   {
 953:   }
 954: 
 955:   protected void uninstallListeners() 
 956:   {
 957:     table.removeFocusListener(focusListener);  
 958:     table.removeKeyListener(keyListener);
 959:     table.removeMouseListener(mouseInputListener);    
 960:     table.removeMouseMotionListener(mouseInputListener);
 961:   }
 962: 
 963:   public void installUI(JComponent comp) 
 964:   {
 965:     table = (JTable)comp;
 966:     focusListener = createFocusListener();  
 967:     mouseInputListener = createMouseInputListener();
 968:     installDefaults();
 969:     installKeyboardActions();
 970:     installListeners();
 971:   }
 972: 
 973:   public void uninstallUI(JComponent c) 
 974:   {
 975:     uninstallListeners();
 976:     uninstallKeyboardActions();
 977:     uninstallDefaults();    
 978:   }
 979: 
 980:   public void paint(Graphics gfx, JComponent ignored) 
 981:   {
 982:     int ncols = table.getColumnCount();
 983:     int nrows = table.getRowCount();
 984:     if (nrows == 0 || ncols == 0)
 985:       return;
 986: 
 987:     Rectangle clip = gfx.getClipBounds();
 988:     TableColumnModel cols = table.getColumnModel();
 989: 
 990:     int height = table.getRowHeight();
 991:     int x0 = 0, y0 = 0;
 992:     int x = x0;
 993:     int y = y0;
 994: 
 995:     Dimension gap = table.getIntercellSpacing();
 996:     int ymax = clip.y + clip.height;
 997:     int xmax = clip.x + clip.width;
 998: 
 999:     // paint the cell contents
1000:     for (int c = 0; c < ncols && x < xmax; ++c)
1001:       {
1002:         y = y0;
1003:         TableColumn col = cols.getColumn(c);
1004:         int width = col.getWidth();
1005:         int modelCol = col.getModelIndex();
1006: 
1007:         for (int r = 0; r < nrows && y < ymax; ++r)
1008:           {
1009:             Rectangle bounds = new Rectangle(x, y, width, height);
1010:               if (bounds.intersects(clip))
1011:               {
1012:                 TableCellRenderer rend = table.getCellRenderer(r, c);
1013:                 Component comp = table.prepareRenderer(rend, r, c);
1014:                 gfx.translate(x, y);
1015:                 comp.setBounds(new Rectangle(0, 0, width, height));
1016:                 // Set correct border on cell renderer.
1017:                 // Only the lead selection cell gets a border
1018:                 if (comp instanceof JComponent)
1019:                   {
1020:                     if (table.getSelectionModel().getLeadSelectionIndex() == r
1021:                         && table.getColumnModel().getSelectionModel().
1022:                         getLeadSelectionIndex() == c)
1023:                       ((JComponent) comp).setBorder(highlightCellBorder);
1024:                     else
1025:                       ((JComponent) comp).setBorder(cellBorder);
1026:                   }
1027:                 comp.paint(gfx);
1028:                 if (comp instanceof JTextField)
1029:                   ((JTextField)comp).getCaret().paint(gfx);
1030:                 gfx.translate(-x, -y);
1031:               }
1032:               y += height;
1033:               if (gap != null)
1034:                 y += gap.height;
1035:           }
1036:         x += width;
1037:         if (gap != null)
1038:           x += gap.width;
1039:       }
1040: 
1041:     // tighten up the x and y max bounds
1042:     ymax = y;
1043:     xmax = x;
1044: 
1045:     Color grid = table.getGridColor();    
1046: 
1047:     // paint vertical grid lines    
1048:     if (grid != null && table.getShowVerticalLines())
1049:       {    
1050:         x = x0;
1051:         Color save = gfx.getColor();
1052:         gfx.setColor(grid);
1053:         boolean paintedLine = false;
1054:         for (int c = 0; c < ncols && x < xmax; ++c)
1055:           {
1056:             x += cols.getColumn(c).getWidth();;
1057:             if (gap != null)
1058:               x += gap.width;
1059:             gfx.drawLine(x, y0, x, ymax);
1060:             paintedLine = true;
1061:           }
1062:         gfx.setColor(save);
1063:       }
1064: 
1065:     // paint horizontal grid lines    
1066:     if (grid != null && table.getShowHorizontalLines())
1067:       {    
1068:         y = y0;
1069:         Color save = gfx.getColor();
1070:         gfx.setColor(grid);
1071:         boolean paintedLine = false;
1072:         for (int r = 0; r < nrows && y < ymax; ++r)
1073:           {
1074:             y += height;
1075:             if (gap != null)
1076:               y += gap.height;
1077:             gfx.drawLine(x0, y, xmax, y);
1078:             paintedLine = true;
1079:           }
1080:         gfx.setColor(save);
1081:       }
1082:   }
1083: }