Source for javax.swing.plaf.basic.BasicScrollPaneUI

   1: /* BasicScrollPaneUI.java
   2:    Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.plaf.basic;
  40: 
  41: import gnu.classpath.NotImplementedException;
  42: 
  43: import java.awt.Component;
  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.ContainerEvent;
  50: import java.awt.event.ContainerListener;
  51: import java.awt.event.MouseWheelEvent;
  52: import java.awt.event.MouseWheelListener;
  53: import java.beans.PropertyChangeEvent;
  54: import java.beans.PropertyChangeListener;
  55: 
  56: import javax.swing.AbstractAction;
  57: import javax.swing.Action;
  58: import javax.swing.ActionMap;
  59: import javax.swing.InputMap;
  60: import javax.swing.JComponent;
  61: import javax.swing.JScrollBar;
  62: import javax.swing.JScrollPane;
  63: import javax.swing.JSlider;
  64: import javax.swing.JViewport;
  65: import javax.swing.LookAndFeel;
  66: import javax.swing.ScrollPaneConstants;
  67: import javax.swing.ScrollPaneLayout;
  68: import javax.swing.Scrollable;
  69: import javax.swing.SwingConstants;
  70: import javax.swing.SwingUtilities;
  71: import javax.swing.UIManager;
  72: import javax.swing.event.ChangeEvent;
  73: import javax.swing.event.ChangeListener;
  74: import javax.swing.plaf.ActionMapUIResource;
  75: import javax.swing.plaf.ComponentUI;
  76: import javax.swing.plaf.ScrollPaneUI;
  77: 
  78: /**
  79:  * A UI delegate for the {@link JScrollPane} component.
  80:  */
  81: public class BasicScrollPaneUI extends ScrollPaneUI
  82:   implements ScrollPaneConstants
  83: {
  84: 
  85:   /**
  86:    * Listens for changes in the state of the horizontal scrollbar's model and
  87:    * updates the scrollpane accordingly.
  88:    *
  89:    * @author Roman Kennke (kennke@aicas.com)
  90:    */
  91:   public class HSBChangeListener implements ChangeListener
  92:   {
  93: 
  94:     /**
  95:      * Receives notification when the state of the horizontal scrollbar
  96:      * model has changed.
  97:      *
  98:      * @param event the change event
  99:      */
 100:     public void stateChanged(ChangeEvent event)
 101:     {
 102:       JScrollBar hsb = scrollpane.getHorizontalScrollBar();
 103:       JViewport vp = scrollpane.getViewport();
 104:       Point viewPosition = vp.getViewPosition();
 105:       int xpos = hsb.getValue();
 106: 
 107:       if (xpos != viewPosition.x)
 108:         {
 109:           viewPosition.x = xpos;
 110:           vp.setViewPosition(viewPosition);
 111:         }
 112: 
 113:       viewPosition.y = 0;
 114:       JViewport columnHeader = scrollpane.getColumnHeader();
 115:       if (columnHeader != null 
 116:           && !columnHeader.getViewPosition().equals(viewPosition))
 117:         columnHeader.setViewPosition(viewPosition);
 118:     }
 119: 
 120:   }
 121: 
 122:   /**
 123:    * Listens for changes in the state of the vertical scrollbar's model and
 124:    * updates the scrollpane accordingly.
 125:    *
 126:    * @author Roman Kennke (kennke@aicas.com)
 127:    */
 128:   public class VSBChangeListener implements ChangeListener
 129:   {
 130: 
 131:     /**
 132:      * Receives notification when the state of the vertical scrollbar
 133:      * model has changed.
 134:      *
 135:      * @param event the change event
 136:      */
 137:     public void stateChanged(ChangeEvent event)
 138:     {
 139:       JScrollBar vsb = scrollpane.getVerticalScrollBar();
 140:       JViewport vp = scrollpane.getViewport();
 141:       Point viewPosition = vp.getViewPosition();
 142:       int ypos = vsb.getValue();
 143:       if (ypos != viewPosition.y)
 144:         {
 145:           viewPosition.y = ypos;
 146:           vp.setViewPosition(viewPosition);
 147:         }
 148: 
 149:       viewPosition.x = 0;
 150:       JViewport rowHeader = scrollpane.getRowHeader();
 151:       if (rowHeader != null 
 152:           && !rowHeader.getViewPosition().equals(viewPosition))
 153:         rowHeader.setViewPosition(viewPosition);
 154:     }
 155:  
 156:   }
 157: 
 158:   /**
 159:    * Listens for changes of the viewport's extent size and updates the
 160:    * scrollpane accordingly.
 161:    *
 162:    * @author Roman Kennke (kennke@aicas.com)
 163:    */
 164:   public class ViewportChangeHandler implements ChangeListener
 165:   {
 166: 
 167:     /**
 168:      * Receives notification when the view's size, position or extent size
 169:      * changes. When the extents size has changed, this method calls
 170:      * {@link BasicScrollPaneUI#syncScrollPaneWithViewport()} to adjust the
 171:      * scrollbars extents as well.
 172:      * 
 173:      * @param event the change event
 174:      */
 175:     public void stateChanged(ChangeEvent event)
 176:     {
 177:       JViewport vp = scrollpane.getViewport();
 178:       JScrollBar hsb = scrollpane.getHorizontalScrollBar();
 179:       JScrollBar vsb = scrollpane.getVerticalScrollBar();
 180:       syncScrollPaneWithViewport();
 181:     }
 182: 
 183:   }
 184: 
 185:   /**
 186:    * Listens for property changes on the scrollpane and update the view
 187:    * accordingly.
 188:    *
 189:    * @author Roman Kennke (kennke@aicas.com)
 190:    */
 191:   public class PropertyChangeHandler implements PropertyChangeListener
 192:   {
 193: 
 194:     /**
 195:      * Receives notification when any of the scrollpane's bound property
 196:      * changes. This method calls the appropriate update method on the
 197:      * <code>ScrollBarUI</code>.
 198:      *
 199:      * @param e the property change event
 200:      *
 201:      * @see BasicScrollPaneUI#updateColumnHeader(PropertyChangeEvent)
 202:      * @see BasicScrollPaneUI#updateRowHeader(PropertyChangeEvent)
 203:      * @see BasicScrollPaneUI#updateScrollBarDisplayPolicy(PropertyChangeEvent)
 204:      * @see BasicScrollPaneUI#updateViewport(PropertyChangeEvent)
 205:      */
 206:     public void propertyChange(PropertyChangeEvent e)
 207:     {
 208:       String propName = e.getPropertyName();
 209:       if (propName.equals("viewport"))
 210:         updateViewport(e);
 211:       else if (propName.equals("rowHeader"))
 212:         updateRowHeader(e);
 213:       else if (propName.equals("columnHeader"))
 214:         updateColumnHeader(e);
 215:       else if (propName.equals("horizontalScrollBarPolicy")
 216:           || e.getPropertyName().equals("verticalScrollBarPolicy"))
 217:         updateScrollBarDisplayPolicy(e);
 218:       else if (propName.equals("verticalScrollBar"))
 219:         {
 220:           JScrollBar oldSb = (JScrollBar) e.getOldValue();
 221:           oldSb.getModel().removeChangeListener(vsbChangeListener);
 222:           JScrollBar newSb = (JScrollBar) e.getNewValue();
 223:           newSb.getModel().addChangeListener(vsbChangeListener);
 224:         }
 225:       else if (propName.equals("horizontalScrollBar"))
 226:         {
 227:           JScrollBar oldSb = (JScrollBar) e.getOldValue();
 228:           oldSb.getModel().removeChangeListener(hsbChangeListener);
 229:           JScrollBar newSb = (JScrollBar) e.getNewValue();
 230:           newSb.getModel().addChangeListener(hsbChangeListener);
 231:         }
 232:     }
 233: 
 234:   }
 235: 
 236:   /**
 237:    * Listens for mouse wheel events and update the scrollpane accordingly.
 238:    *
 239:    * @author Roman Kennke (kennke@aicas.com)
 240:    *
 241:    * @since 1.4
 242:    */
 243:   protected class MouseWheelHandler implements MouseWheelListener
 244:   {
 245:     /**
 246:      * Use to compute the visible rectangle.
 247:      */
 248:     final Rectangle rect = new Rectangle();
 249: 
 250:     /**
 251:      * Scroll with the mouse wheel.
 252:      * 
 253:      * @author Audrius Meskauskas (audriusa@Bioinformatics.org)
 254:      */
 255:     public void mouseWheelMoved(MouseWheelEvent e)
 256:     {
 257:       if (scrollpane.getViewport().getComponentCount() == 0)
 258:         return;
 259: 
 260:       Component target = scrollpane.getViewport().getComponent(0);
 261:       JScrollBar bar = scrollpane.getVerticalScrollBar();
 262:       Scrollable scrollable = (target instanceof Scrollable) ? (Scrollable) target
 263:                                                             : null;
 264: 
 265:       boolean tracksHeight = scrollable != null
 266:                              && scrollable.getScrollableTracksViewportHeight();
 267:       int wheel = e.getWheelRotation() * ROWS_PER_WHEEL_CLICK;
 268:       int delta;
 269: 
 270:       // If possible, scroll vertically.
 271:       if (bar != null && ! tracksHeight)
 272:         {
 273:           if (scrollable != null)
 274:             {
 275:               bounds(target);
 276:               delta = scrollable.getScrollableUnitIncrement(
 277:                 rect, SwingConstants.VERTICAL, wheel);
 278:             }
 279:           else
 280:             {
 281:               // Scroll non scrollables.
 282:               delta = wheel * SCROLL_NON_SCROLLABLES;
 283:             }
 284:           scroll(bar, delta);
 285:         }
 286:       // If not, try to scroll horizontally
 287:       else
 288:         {
 289:           bar = scrollpane.getHorizontalScrollBar();
 290:           boolean tracksWidth = scrollable != null
 291:                                 && scrollable.getScrollableTracksViewportWidth();
 292: 
 293:           if (bar != null && ! tracksWidth)
 294:             {
 295:               if (scrollable != null)
 296:                 {
 297:                   bounds(target);
 298:                   delta = scrollable.getScrollableUnitIncrement(
 299:                      rect, SwingConstants.HORIZONTAL, wheel);
 300:                 }
 301:               else
 302:                 {
 303:                   // Scroll non scrollables.
 304:                   delta = wheel * SCROLL_NON_SCROLLABLES;
 305:                 }
 306:               scroll(bar, delta);
 307:             }
 308:         }
 309:     }
 310:     
 311:     /**
 312:      * Place the component bounds into rect. The x and y values 
 313:      * need to be reversed.
 314:      * 
 315:      * @param target the target being scrolled
 316:      */
 317:     final void bounds(Component target)
 318:     {
 319:       // Viewport bounds, translated by the scroll bar positions.
 320:       target.getParent().getBounds(rect);
 321:       rect.x = getValue(scrollpane.getHorizontalScrollBar());
 322:       rect.y = getValue(scrollpane.getVerticalScrollBar());
 323:     }
 324:     
 325:     /**
 326:      * Get the scroll bar value or 0 if there is no such scroll bar.
 327:      * 
 328:      * @param bar  the scroll bar (<code>null</code> permitted).
 329:      * 
 330:      * @return The scroll bar value, or 0.
 331:      */
 332:     final int getValue(JScrollBar bar)
 333:     {
 334:       return bar != null ? bar.getValue() : 0;
 335:     }
 336:     
 337:     /**
 338:      * Scroll the given distance.
 339:      * 
 340:      * @param bar the scrollbar to scroll
 341:      * @param delta the distance
 342:      */
 343:     final void scroll(JScrollBar bar, int delta)
 344:     {
 345:       int y = bar.getValue() + delta;
 346: 
 347:       if (y < bar.getMinimum())
 348:         y = bar.getMinimum();
 349:       if (y > bar.getMaximum())
 350:         y = bar.getMaximum();
 351: 
 352:       bar.setValue(y);
 353:     }
 354:   }
 355:   
 356:   /**
 357:    * Adds/removes the mouse wheel listener when the component is added/removed
 358:    * to/from the scroll pane view port.
 359:    * 
 360:    * @author Audrius Meskauskas (audriusa@bioinformatics.org)
 361:    */
 362:   class ViewportContainerListener implements ContainerListener
 363:   {
 364:     /**
 365:      * Add the mouse wheel listener, allowing to scroll with the mouse.
 366:      */
 367:     public void componentAdded(ContainerEvent e)
 368:     {
 369:       e.getChild().addMouseWheelListener(mouseWheelListener);
 370:     }
 371:     
 372:     /**
 373:      * Remove the mouse wheel listener.
 374:      */
 375:     public void componentRemoved(ContainerEvent e)
 376:     {
 377:       e.getChild().removeMouseWheelListener(mouseWheelListener);
 378:     }
 379:   }
 380:   
 381:   /**
 382:    * The number of pixels by that we should scroll the content that does
 383:    * not implement Scrollable.
 384:    */
 385:   static int SCROLL_NON_SCROLLABLES = 10;
 386:   
 387:   /**
 388:    * The number of rows to scroll per mouse wheel click. From impression,
 389:    * Sun seems using the value 3.
 390:    */
 391:   static int ROWS_PER_WHEEL_CLICK = 3;     
 392: 
 393:   /** The Scrollpane for which the UI is provided by this class. */
 394:   protected JScrollPane scrollpane;
 395: 
 396:   /**
 397:    * The horizontal scrollbar listener.
 398:    */
 399:   protected ChangeListener hsbChangeListener;
 400: 
 401:   /**
 402:    * The vertical scrollbar listener.
 403:    */
 404:   protected ChangeListener vsbChangeListener;
 405: 
 406:   /**
 407:    * The viewport listener.
 408:    */
 409:   protected ChangeListener viewportChangeListener;
 410: 
 411:   /**
 412:    * The scrollpane property change listener.
 413:    */
 414:   protected PropertyChangeListener spPropertyChangeListener;
 415: 
 416:   /**
 417:    * The mousewheel listener for the scrollpane.
 418:    */
 419:   MouseWheelListener mouseWheelListener;
 420:   
 421:   /**
 422:    * The listener to add and remove the mouse wheel listener to/from
 423:    * the component container.
 424:    */
 425:   ContainerListener containerListener;
 426: 
 427:   public static ComponentUI createUI(final JComponent c) 
 428:   {
 429:     return new BasicScrollPaneUI();
 430:   }
 431: 
 432:   protected void installDefaults(JScrollPane p)
 433:   {
 434:     scrollpane = p;
 435:     LookAndFeel.installColorsAndFont(p, "ScrollPane.background",
 436:                                      "ScrollPane.foreground",
 437:                                      "ScrollPane.font");
 438:     LookAndFeel.installBorder(p, "ScrollPane.border");
 439:     p.setOpaque(true);
 440:   }
 441: 
 442:   protected void uninstallDefaults(JScrollPane p)
 443:   {
 444:     p.setForeground(null);
 445:     p.setBackground(null);
 446:     p.setFont(null);
 447:     p.setBorder(null);
 448:     scrollpane = null;
 449:   }
 450:     
 451:   public void installUI(final JComponent c) 
 452:   {
 453:     super.installUI(c);
 454:     installDefaults((JScrollPane) c);
 455:     installListeners((JScrollPane) c);
 456:     installKeyboardActions((JScrollPane) c);
 457:   }
 458: 
 459:   /**
 460:    * Installs the listeners on the scrollbars, the viewport and the scrollpane.
 461:    *
 462:    * @param sp the scrollpane on which to install the listeners
 463:    */
 464:   protected void installListeners(JScrollPane sp)
 465:   {
 466:     if (spPropertyChangeListener == null)
 467:       spPropertyChangeListener = createPropertyChangeListener();
 468:     sp.addPropertyChangeListener(spPropertyChangeListener);
 469: 
 470:     if (hsbChangeListener == null)
 471:       hsbChangeListener = createHSBChangeListener();
 472:     sp.getHorizontalScrollBar().getModel().addChangeListener(hsbChangeListener);
 473:     
 474:     if (vsbChangeListener == null)
 475:       vsbChangeListener = createVSBChangeListener();
 476:     sp.getVerticalScrollBar().getModel().addChangeListener(vsbChangeListener);
 477: 
 478:     if (viewportChangeListener == null)
 479:       viewportChangeListener = createViewportChangeListener();
 480:     
 481:     if (mouseWheelListener == null)
 482:       mouseWheelListener = createMouseWheelListener();
 483:     
 484:     if (containerListener == null)
 485:       containerListener = new ViewportContainerListener();
 486:     
 487:     JViewport v = sp.getViewport();
 488:     v.addChangeListener(viewportChangeListener);
 489:     v.addContainerListener(containerListener);
 490:     
 491:     // Add mouse wheel listeners to the componets that are probably already
 492:     // in the view port.
 493:     for (int i = 0; i < v.getComponentCount(); i++)
 494:       v.getComponent(i).addMouseWheelListener(mouseWheelListener);
 495:   }
 496: 
 497:   InputMap getInputMap(int condition) 
 498:   {
 499:     if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
 500:       return (InputMap) UIManager.get("ScrollPane.ancestorInputMap");
 501:     return null;
 502:   }
 503: 
 504:   /**
 505:    * Returns the action map for the {@link JScrollPane}.  All scroll panes 
 506:    * share a single action map which is created the first time this method is 
 507:    * called, then stored in the UIDefaults table for subsequent access.
 508:    * 
 509:    * @return The shared action map.
 510:    */
 511:   ActionMap getActionMap() 
 512:   {
 513:     ActionMap map = (ActionMap) UIManager.get("ScrollPane.actionMap");
 514: 
 515:     if (map == null) // first time here
 516:       {
 517:         map = createActionMap();
 518:         if (map != null)
 519:           UIManager.put("ScrollPane.actionMap", map);
 520:       }
 521:     return map;
 522:   }
 523: 
 524:   /**
 525:    * Creates the action map shared by all {@link JSlider} instances.
 526:    * This method is called once by {@link #getActionMap()} when it 
 527:    * finds no action map in the UIDefaults table...after the map is 
 528:    * created, it gets added to the defaults table so that subsequent 
 529:    * calls to {@link #getActionMap()} will return the same shared 
 530:    * instance.
 531:    * 
 532:    * @return The action map.
 533:    */
 534:   ActionMap createActionMap()
 535:   {
 536:     ActionMap map = new ActionMapUIResource();
 537:     map.put("scrollLeft", 
 538:             new AbstractAction("scrollLeft") {
 539:               public void actionPerformed(ActionEvent event)
 540:               {
 541:                 JScrollPane sp = (JScrollPane) event.getSource();
 542:                 JScrollBar sb = sp.getHorizontalScrollBar();
 543:                 if (sb.isVisible()) 
 544:                   {
 545:                     int delta = sb.getBlockIncrement(-1);
 546:                     sb.setValue(sb.getValue() + delta);
 547:                   }
 548:               }
 549:             }
 550:     );
 551:     map.put("scrollEnd", 
 552:             new AbstractAction("scrollEnd") {
 553:               public void actionPerformed(ActionEvent event)
 554:               {
 555:                 JScrollPane sp = (JScrollPane) event.getSource();
 556:                 JScrollBar sb1 = sp.getHorizontalScrollBar();
 557:                 if (sb1.isVisible()) 
 558:                   {
 559:                     sb1.setValue(sb1.getMaximum());
 560:                   }
 561:                 JScrollBar sb2 = sp.getVerticalScrollBar();
 562:                 if (sb2.isVisible()) 
 563:                   {
 564:                     sb2.setValue(sb2.getMaximum());
 565:                   }
 566:               }
 567:             }
 568:     );
 569:     map.put("unitScrollUp", 
 570:             new AbstractAction("unitScrollUp") {
 571:               public void actionPerformed(ActionEvent event)
 572:               {
 573:                 JScrollPane sp = (JScrollPane) event.getSource();
 574:                 JScrollBar sb = sp.getVerticalScrollBar();
 575:                 if (sb.isVisible()) 
 576:                   {
 577:                     int delta = sb.getUnitIncrement(-1);
 578:                     sb.setValue(sb.getValue() + delta);
 579:                   }
 580:               }
 581:             }
 582:     );
 583:     map.put("unitScrollLeft", 
 584:             new AbstractAction("unitScrollLeft") {
 585:               public void actionPerformed(ActionEvent event)
 586:               {
 587:                 JScrollPane sp = (JScrollPane) event.getSource();
 588:                 JScrollBar sb = sp.getHorizontalScrollBar();
 589:                 if (sb.isVisible()) 
 590:                   {
 591:                     int delta = sb.getUnitIncrement(-1);
 592:                     sb.setValue(sb.getValue() + delta);
 593:                   }
 594:               }
 595:             }
 596:     );
 597:     map.put("scrollUp", 
 598:             new AbstractAction("scrollUp") {
 599:               public void actionPerformed(ActionEvent event)
 600:               {
 601:                 JScrollPane sp = (JScrollPane) event.getSource();
 602:                 JScrollBar sb = sp.getVerticalScrollBar();
 603:                 if (sb.isVisible()) 
 604:                   {
 605:                     int delta = sb.getBlockIncrement(-1);
 606:                     sb.setValue(sb.getValue() + delta);
 607:                   }
 608:               }
 609:             }
 610:     );
 611:     map.put("scrollRight", 
 612:             new AbstractAction("scrollRight") {
 613:               public void actionPerformed(ActionEvent event)
 614:               {
 615:                 JScrollPane sp = (JScrollPane) event.getSource();
 616:                 JScrollBar sb = sp.getHorizontalScrollBar();
 617:                 if (sb.isVisible()) 
 618:                   {
 619:                     int delta = sb.getBlockIncrement(1);
 620:                     sb.setValue(sb.getValue() + delta);
 621:                   }
 622:               }
 623:             }
 624:     );
 625:     map.put("scrollHome", 
 626:             new AbstractAction("scrollHome") {
 627:               public void actionPerformed(ActionEvent event)
 628:               {
 629:                 JScrollPane sp = (JScrollPane) event.getSource();
 630:                 JScrollBar sb1 = sp.getHorizontalScrollBar();
 631:                 if (sb1.isVisible()) 
 632:                   {
 633:                     sb1.setValue(sb1.getMinimum());
 634:                   }
 635:                 JScrollBar sb2 = sp.getVerticalScrollBar();
 636:                 if (sb2.isVisible()) 
 637:                   {
 638:                     sb2.setValue(sb2.getMinimum());
 639:                   }
 640:               }
 641:             }
 642:     );
 643:     map.put("scrollDown", 
 644:             new AbstractAction("scrollDown") {
 645:               public void actionPerformed(ActionEvent event)
 646:               {
 647:                 JScrollPane sp = (JScrollPane) event.getSource();
 648:                 JScrollBar sb = sp.getVerticalScrollBar();
 649:                 if (sb.isVisible()) 
 650:                   {
 651:                     int delta = sb.getBlockIncrement(1);
 652:                     sb.setValue(sb.getValue() + delta);
 653:                   }
 654:               }
 655:             }
 656:     );
 657:     map.put("unitScrollDown", 
 658:             new AbstractAction("unitScrollDown") {
 659:               public void actionPerformed(ActionEvent event)
 660:               {
 661:                 JScrollPane sp = (JScrollPane) event.getSource();
 662:                 JScrollBar sb = sp.getVerticalScrollBar();
 663:                 if (sb.isVisible()) 
 664:                   {
 665:                     int delta = sb.getUnitIncrement(1);
 666:                     sb.setValue(sb.getValue() + delta);
 667:                   }
 668:               }
 669:             }
 670:     );
 671:     map.put("unitScrollRight", 
 672:             new AbstractAction("unitScrollRight") {
 673:               public void actionPerformed(ActionEvent event)
 674:               {
 675:                 JScrollPane sp = (JScrollPane) event.getSource();
 676:                 JScrollBar sb = sp.getHorizontalScrollBar();
 677:                 if (sb.isVisible()) 
 678:                   {
 679:                     int delta = sb.getUnitIncrement(1);
 680:                     sb.setValue(sb.getValue() + delta);
 681:                   }
 682:               }
 683:             }
 684:     );
 685:     return map;
 686:   }
 687:   
 688:   /**
 689:    * Installs additional keyboard actions on the scrollpane. This is a hook
 690:    * method provided to subclasses in order to install their own keyboard
 691:    * actions.
 692:    *
 693:    * @param sp the scrollpane to install keyboard actions on
 694:    */
 695:   protected void installKeyboardActions(JScrollPane sp)
 696:   {
 697:     InputMap keyMap = getInputMap(
 698:         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 699:     SwingUtilities.replaceUIInputMap(sp, 
 700:         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keyMap);
 701:     ActionMap map = getActionMap();
 702:     SwingUtilities.replaceUIActionMap(sp, map);
 703:   }
 704: 
 705:   /**
 706:    * Uninstalls all keyboard actions from the JScrollPane that have been
 707:    * installed by {@link #installKeyboardActions}. This is a hook method
 708:    * provided to subclasses to add their own keyboard actions.
 709:    *
 710:    * @param sp the scrollpane to uninstall keyboard actions from
 711:    */
 712:   protected void uninstallKeyboardActions(JScrollPane sp)
 713:   {
 714:     SwingUtilities.replaceUIActionMap(sp, null);
 715:     SwingUtilities.replaceUIInputMap(sp, 
 716:         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
 717:   }
 718:   
 719:   /**
 720:    * Creates and returns the change listener for the horizontal scrollbar.
 721:    *
 722:    * @return the change listener for the horizontal scrollbar
 723:    */
 724:   protected ChangeListener createHSBChangeListener()
 725:   {
 726:     return new HSBChangeListener();
 727:   }
 728: 
 729:   /**
 730:    * Creates and returns the change listener for the vertical scrollbar.
 731:    *
 732:    * @return the change listener for the vertical scrollbar
 733:    */
 734:   protected ChangeListener createVSBChangeListener()
 735:   {
 736:     return new VSBChangeListener();
 737:   }
 738: 
 739:   /**
 740:    * Creates and returns the change listener for the viewport.
 741:    *
 742:    * @return the change listener for the viewport
 743:    */
 744:   protected ChangeListener createViewportChangeListener()
 745:   {
 746:     return new ViewportChangeHandler();
 747:   }
 748: 
 749:   /**
 750:    * Creates and returns the property change listener for the scrollpane.
 751:    *
 752:    * @return the property change listener for the scrollpane
 753:    */
 754:   protected PropertyChangeListener createPropertyChangeListener()
 755:   {
 756:     return new PropertyChangeHandler();
 757:   }
 758: 
 759:   /**
 760:    * Creates and returns the mouse wheel listener for the scrollpane.
 761:    *
 762:    * @return the mouse wheel listener for the scrollpane
 763:    * 
 764:    * @since 1.4
 765:    */
 766:   protected MouseWheelListener createMouseWheelListener()
 767:   {
 768:     return new MouseWheelHandler();
 769:   }
 770: 
 771:   public void uninstallUI(final JComponent c) 
 772:   {
 773:     super.uninstallUI(c);
 774:     this.uninstallDefaults((JScrollPane) c);
 775:     uninstallListeners((JScrollPane) c);
 776:     installKeyboardActions((JScrollPane) c);
 777:   }
 778: 
 779:   /**
 780:    * Uninstalls all the listeners that have been installed in
 781:    * {@link #installListeners(JScrollPane)}.
 782:    *
 783:    * @param c the scrollpane from which to uninstall the listeners 
 784:    */
 785:   protected void uninstallListeners(JComponent c)
 786:   {
 787:     JScrollPane sp = (JScrollPane) c;
 788:     sp.removePropertyChangeListener(spPropertyChangeListener);
 789:     sp.getHorizontalScrollBar().getModel()
 790:                                .removeChangeListener(hsbChangeListener);
 791:     sp.getVerticalScrollBar().getModel()
 792:                              .removeChangeListener(vsbChangeListener);
 793:     
 794:     JViewport v = sp.getViewport();
 795:     v.removeChangeListener(viewportChangeListener);
 796:     v.removeContainerListener(containerListener);
 797:  
 798:     for (int i = 0; i < v.getComponentCount(); i++)
 799:       v.getComponent(i).removeMouseWheelListener(mouseWheelListener);
 800: 
 801:   }
 802: 
 803:   public Dimension getMinimumSize(JComponent c) 
 804:   {
 805:     JScrollPane p = (JScrollPane) c;
 806:     ScrollPaneLayout sl = (ScrollPaneLayout) p.getLayout();
 807:     return sl.minimumLayoutSize(c);
 808:   }
 809: 
 810:   public void paint(Graphics g, JComponent c)
 811:   {      
 812:     // do nothing; the normal painting-of-children algorithm, along with
 813:     // ScrollPaneLayout, does all the relevant work.
 814:   }
 815: 
 816:   /**
 817:    * Synchronizes the scrollbars with the viewport's extents.
 818:    */
 819:   protected void syncScrollPaneWithViewport()
 820:   {
 821:     JViewport vp = scrollpane.getViewport();
 822: 
 823:     // Update the horizontal scrollbar.
 824:     JScrollBar hsb = scrollpane.getHorizontalScrollBar();
 825:     hsb.setMaximum(vp.getViewSize().width);
 826:     hsb.setValue(vp.getViewPosition().x);
 827:     hsb.setVisibleAmount(vp.getExtentSize().width);
 828:     
 829:     // Update the vertical scrollbar.
 830:     JScrollBar vsb = scrollpane.getVerticalScrollBar();
 831:     vsb.setMaximum(vp.getViewSize().height);
 832:     vsb.setValue(vp.getViewPosition().y);
 833:     vsb.setVisibleAmount(vp.getExtentSize().height);
 834:   }
 835: 
 836:   /**
 837:    * Receives notification when the <code>columnHeader</code> property has
 838:    * changed on the scrollpane.
 839:    *
 840:    * @param ev the property change event
 841:    */
 842:   protected void updateColumnHeader(PropertyChangeEvent ev)
 843:   {
 844:     // TODO: Find out what should be done here. Or is this only a hook?
 845:   }
 846: 
 847:   /**
 848:    * Receives notification when the <code>rowHeader</code> property has changed
 849:    * on the scrollpane.
 850:    *
 851:    * @param ev the property change event
 852:    */
 853:   protected void updateRowHeader(PropertyChangeEvent ev)
 854:   {
 855:     // TODO: Find out what should be done here. Or is this only a hook?
 856:   }
 857: 
 858:   /**
 859:    * Receives notification when the <code>scrollBarDisplayPolicy</code>
 860:    * property has changed on the scrollpane.
 861:    *
 862:    * @param ev the property change event
 863:    */
 864:   protected void updateScrollBarDisplayPolicy(PropertyChangeEvent ev)
 865:   {
 866:     // TODO: Find out what should be done here. Or is this only a hook?
 867:   }
 868: 
 869:   /**
 870:    * Receives notification when the <code>viewport</code> property has changed
 871:    * on the scrollpane.
 872:    *
 873:    * This method sets removes the viewportChangeListener from the old viewport
 874:    * and adds it to the new viewport.
 875:    *
 876:    * @param ev the property change event
 877:    */
 878:   protected void updateViewport(PropertyChangeEvent ev)
 879:   {
 880:     JViewport oldViewport = (JViewport) ev.getOldValue();
 881:     oldViewport.removeChangeListener(viewportChangeListener);
 882:     JViewport newViewport = (JViewport) ev.getNewValue();
 883:     newViewport.addChangeListener(viewportChangeListener);
 884:     syncScrollPaneWithViewport();
 885:   }
 886: }
 887: 
 888: 
 889: 
 890: 
 891: 
 892: 
 893: 
 894: 
 895: 
 896: 
 897: