1 /*
   2  * Copyright (c) 1997, 2009, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.swing.plaf.basic;
  27 
  28 import sun.swing.DefaultLookup;
  29 import sun.swing.UIAction;
  30 
  31 import javax.swing.*;
  32 import javax.swing.event.*;
  33 import javax.swing.border.*;
  34 import javax.swing.plaf.*;
  35 
  36 import java.beans.PropertyChangeListener;
  37 import java.beans.PropertyChangeEvent;
  38 
  39 import java.awt.Component;
  40 import java.awt.Rectangle;
  41 import java.awt.Dimension;
  42 import java.awt.Point;
  43 import java.awt.Insets;
  44 import java.awt.Graphics;
  45 import java.awt.event.*;
  46 
  47 /**
  48  * A default L&F implementation of ScrollPaneUI.
  49  *
  50  * @author Hans Muller
  51  */
  52 public class BasicScrollPaneUI
  53     extends ScrollPaneUI implements ScrollPaneConstants
  54 {
  55     protected JScrollPane scrollpane;
  56     protected ChangeListener vsbChangeListener;
  57     protected ChangeListener hsbChangeListener;
  58     protected ChangeListener viewportChangeListener;
  59     protected PropertyChangeListener spPropertyChangeListener;
  60     private MouseWheelListener mouseScrollListener;
  61     private int oldExtent = Integer.MIN_VALUE;
  62 
  63     /**
  64      * PropertyChangeListener installed on the vertical scrollbar.
  65      */
  66     private PropertyChangeListener vsbPropertyChangeListener;
  67 
  68     /**
  69      * PropertyChangeListener installed on the horizontal scrollbar.
  70      */
  71     private PropertyChangeListener hsbPropertyChangeListener;
  72 
  73     private Handler handler;
  74 
  75     /**
  76      * State flag that shows whether setValue() was called from a user program
  77      * before the value of "extent" was set in right-to-left component
  78      * orientation.
  79      */
  80     private boolean setValueCalled = false;
  81 
  82 
  83     public static ComponentUI createUI(JComponent x) {
  84         return new BasicScrollPaneUI();
  85     }
  86 
  87     static void loadActionMap(LazyActionMap map) {
  88         map.put(new Actions(Actions.SCROLL_UP));
  89         map.put(new Actions(Actions.SCROLL_DOWN));
  90         map.put(new Actions(Actions.SCROLL_HOME));
  91         map.put(new Actions(Actions.SCROLL_END));
  92         map.put(new Actions(Actions.UNIT_SCROLL_UP));
  93         map.put(new Actions(Actions.UNIT_SCROLL_DOWN));
  94         map.put(new Actions(Actions.SCROLL_LEFT));
  95         map.put(new Actions(Actions.SCROLL_RIGHT));
  96         map.put(new Actions(Actions.UNIT_SCROLL_RIGHT));
  97         map.put(new Actions(Actions.UNIT_SCROLL_LEFT));
  98     }
  99 
 100 
 101 
 102     public void paint(Graphics g, JComponent c) {
 103         Border vpBorder = scrollpane.getViewportBorder();
 104         if (vpBorder != null) {
 105             Rectangle r = scrollpane.getViewportBorderBounds();
 106             vpBorder.paintBorder(scrollpane, g, r.x, r.y, r.width, r.height);
 107         }
 108     }
 109 
 110 
 111     /**
 112      * @return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)
 113      */
 114     public Dimension getMaximumSize(JComponent c) {
 115         return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
 116     }
 117 
 118 
 119     protected void installDefaults(JScrollPane scrollpane)
 120     {
 121         LookAndFeel.installBorder(scrollpane, "ScrollPane.border");
 122         LookAndFeel.installColorsAndFont(scrollpane,
 123             "ScrollPane.background",
 124             "ScrollPane.foreground",
 125             "ScrollPane.font");
 126 
 127         Border vpBorder = scrollpane.getViewportBorder();
 128         if ((vpBorder == null) ||( vpBorder instanceof UIResource)) {
 129             vpBorder = UIManager.getBorder("ScrollPane.viewportBorder");
 130             scrollpane.setViewportBorder(vpBorder);
 131         }
 132         LookAndFeel.installProperty(scrollpane, "opaque", Boolean.TRUE);
 133     }
 134 
 135 
 136     protected void installListeners(JScrollPane c)
 137     {
 138         vsbChangeListener = createVSBChangeListener();
 139         vsbPropertyChangeListener = createVSBPropertyChangeListener();
 140         hsbChangeListener = createHSBChangeListener();
 141         hsbPropertyChangeListener = createHSBPropertyChangeListener();
 142         viewportChangeListener = createViewportChangeListener();
 143         spPropertyChangeListener = createPropertyChangeListener();
 144 
 145         JViewport viewport = scrollpane.getViewport();
 146         JScrollBar vsb = scrollpane.getVerticalScrollBar();
 147         JScrollBar hsb = scrollpane.getHorizontalScrollBar();
 148 
 149         if (viewport != null) {
 150             viewport.addChangeListener(viewportChangeListener);
 151         }
 152         if (vsb != null) {
 153             vsb.getModel().addChangeListener(vsbChangeListener);
 154             vsb.addPropertyChangeListener(vsbPropertyChangeListener);
 155         }
 156         if (hsb != null) {
 157             hsb.getModel().addChangeListener(hsbChangeListener);
 158             hsb.addPropertyChangeListener(hsbPropertyChangeListener);
 159         }
 160 
 161         scrollpane.addPropertyChangeListener(spPropertyChangeListener);
 162 
 163     mouseScrollListener = createMouseWheelListener();
 164     scrollpane.addMouseWheelListener(mouseScrollListener);
 165 
 166     }
 167 
 168     protected void installKeyboardActions(JScrollPane c) {
 169         InputMap inputMap = getInputMap(JComponent.
 170                                   WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 171 
 172         SwingUtilities.replaceUIInputMap(c, JComponent.
 173                                WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap);
 174 
 175         LazyActionMap.installLazyActionMap(c, BasicScrollPaneUI.class,
 176                                            "ScrollPane.actionMap");
 177     }
 178 
 179     InputMap getInputMap(int condition) {
 180         if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
 181             InputMap keyMap = (InputMap)DefaultLookup.get(scrollpane, this,
 182                                         "ScrollPane.ancestorInputMap");
 183             InputMap rtlKeyMap;
 184 
 185             if (scrollpane.getComponentOrientation().isLeftToRight() ||
 186                     ((rtlKeyMap = (InputMap)DefaultLookup.get(scrollpane, this,
 187                     "ScrollPane.ancestorInputMap.RightToLeft")) == null)) {
 188                 return keyMap;
 189             } else {
 190                 rtlKeyMap.setParent(keyMap);
 191                 return rtlKeyMap;
 192             }
 193         }
 194         return null;
 195     }
 196 
 197     public void installUI(JComponent x) {
 198         scrollpane = (JScrollPane)x;
 199         installDefaults(scrollpane);
 200         installListeners(scrollpane);
 201         installKeyboardActions(scrollpane);
 202     }
 203 
 204 
 205     protected void uninstallDefaults(JScrollPane c) {
 206         LookAndFeel.uninstallBorder(scrollpane);
 207 
 208         if (scrollpane.getViewportBorder() instanceof UIResource) {
 209             scrollpane.setViewportBorder(null);
 210         }
 211     }
 212 
 213 
 214     protected void uninstallListeners(JComponent c) {
 215         JViewport viewport = scrollpane.getViewport();
 216         JScrollBar vsb = scrollpane.getVerticalScrollBar();
 217         JScrollBar hsb = scrollpane.getHorizontalScrollBar();
 218 
 219         if (viewport != null) {
 220             viewport.removeChangeListener(viewportChangeListener);
 221         }
 222         if (vsb != null) {
 223             vsb.getModel().removeChangeListener(vsbChangeListener);
 224             vsb.removePropertyChangeListener(vsbPropertyChangeListener);
 225         }
 226         if (hsb != null) {
 227             hsb.getModel().removeChangeListener(hsbChangeListener);
 228             hsb.removePropertyChangeListener(hsbPropertyChangeListener);
 229         }
 230 
 231         scrollpane.removePropertyChangeListener(spPropertyChangeListener);
 232 
 233     if (mouseScrollListener != null) {
 234         scrollpane.removeMouseWheelListener(mouseScrollListener);
 235     }
 236 
 237         vsbChangeListener = null;
 238         hsbChangeListener = null;
 239         viewportChangeListener = null;
 240         spPropertyChangeListener = null;
 241         mouseScrollListener = null;
 242         handler = null;
 243     }
 244 
 245 
 246     protected void uninstallKeyboardActions(JScrollPane c) {
 247         SwingUtilities.replaceUIActionMap(c, null);
 248         SwingUtilities.replaceUIInputMap(c, JComponent.
 249                            WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
 250     }
 251 
 252 
 253     public void uninstallUI(JComponent c) {
 254         uninstallDefaults(scrollpane);
 255         uninstallListeners(scrollpane);
 256         uninstallKeyboardActions(scrollpane);
 257         scrollpane = null;
 258     }
 259 
 260     private Handler getHandler() {
 261         if (handler == null) {
 262             handler = new Handler();
 263         }
 264         return handler;
 265     }
 266 
 267     protected void syncScrollPaneWithViewport()
 268     {
 269         JViewport viewport = scrollpane.getViewport();
 270         JScrollBar vsb = scrollpane.getVerticalScrollBar();
 271         JScrollBar hsb = scrollpane.getHorizontalScrollBar();
 272         JViewport rowHead = scrollpane.getRowHeader();
 273         JViewport colHead = scrollpane.getColumnHeader();
 274         boolean ltr = scrollpane.getComponentOrientation().isLeftToRight();
 275 
 276         if (viewport != null) {
 277             Dimension extentSize = viewport.getExtentSize();
 278             Dimension viewSize = viewport.getViewSize();
 279             Point viewPosition = viewport.getViewPosition();
 280 
 281             if (vsb != null) {
 282                 int extent = extentSize.height;
 283                 int max = viewSize.height;
 284                 int value = Math.max(0, Math.min(viewPosition.y, max - extent));
 285                 vsb.setValues(value, extent, 0, max);
 286             }
 287 
 288             if (hsb != null) {
 289                 int extent = extentSize.width;
 290                 int max = viewSize.width;
 291                 int value;
 292 
 293                 if (ltr) {
 294                     value = Math.max(0, Math.min(viewPosition.x, max - extent));
 295                 } else {
 296                     int currentValue = hsb.getValue();
 297 
 298                     /* Use a particular formula to calculate "value"
 299                      * until effective x coordinate is calculated.
 300                      */
 301                     if (setValueCalled && ((max - currentValue) == viewPosition.x)) {
 302                         value = Math.max(0, Math.min(max - extent, currentValue));
 303                         /* After "extent" is set, turn setValueCalled flag off.
 304                          */
 305                         if (extent != 0) {
 306                             setValueCalled = false;
 307                         }
 308                     } else {
 309                         if (extent > max) {
 310                             viewPosition.x = max - extent;
 311                             viewport.setViewPosition(viewPosition);
 312                             value = 0;
 313                         } else {
 314                            /* The following line can't handle a small value of
 315                             * viewPosition.x like Integer.MIN_VALUE correctly
 316                             * because (max - extent - viewPositoiin.x) causes
 317                             * an overflow. As a result, value becomes zero.
 318                             * (e.g. setViewPosition(Integer.MAX_VALUE, ...)
 319                             *       in a user program causes a overflow.
 320                             *       Its expected value is (max - extent).)
 321                             * However, this seems a trivial bug and adding a
 322                             * fix makes this often-called method slow, so I'll
 323                             * leave it until someone claims.
 324                             */
 325                             value = Math.max(0, Math.min(max - extent, max - extent - viewPosition.x));
 326                             if (oldExtent > extent) {
 327                                 value -= oldExtent - extent;
 328                             }
 329                         }
 330                     }
 331                 }
 332                 oldExtent = extent;
 333                 hsb.setValues(value, extent, 0, max);
 334             }
 335 
 336             if (rowHead != null) {
 337                 Point p = rowHead.getViewPosition();
 338                 p.y = viewport.getViewPosition().y;
 339                 p.x = 0;
 340                 rowHead.setViewPosition(p);
 341             }
 342 
 343             if (colHead != null) {
 344                 Point p = colHead.getViewPosition();
 345                 if (ltr) {
 346                     p.x = viewport.getViewPosition().x;
 347                 } else {
 348                     p.x = Math.max(0, viewport.getViewPosition().x);
 349                 }
 350                 p.y = 0;
 351                 colHead.setViewPosition(p);
 352             }
 353         }
 354     }
 355 
 356     /**
 357      * Returns the baseline.
 358      *
 359      * @throws NullPointerException {@inheritDoc}
 360      * @throws IllegalArgumentException {@inheritDoc}
 361      * @see javax.swing.JComponent#getBaseline(int, int)
 362      * @since 1.6
 363      */
 364     public int getBaseline(JComponent c, int width, int height) {
 365         if (c == null) {
 366             throw new NullPointerException("Component must be non-null");
 367         }
 368 
 369         if (width < 0 || height < 0) {
 370             throw new IllegalArgumentException("Width and height must be >= 0");
 371         }
 372 
 373         JViewport viewport = scrollpane.getViewport();
 374         Insets spInsets = scrollpane.getInsets();
 375         int y = spInsets.top;
 376         height = height - spInsets.top - spInsets.bottom;
 377         width = width - spInsets.left - spInsets.right;
 378         JViewport columnHeader = scrollpane.getColumnHeader();
 379         if (columnHeader != null && columnHeader.isVisible()) {
 380             Component header = columnHeader.getView();
 381             if (header != null && header.isVisible()) {
 382                 // Header is always given it's preferred size.
 383                 Dimension headerPref = header.getPreferredSize();
 384                 int baseline = header.getBaseline(headerPref.width,
 385                                                   headerPref.height);
 386                 if (baseline >= 0) {
 387                     return y + baseline;
 388                 }
 389             }
 390             Dimension columnPref = columnHeader.getPreferredSize();
 391             height -= columnPref.height;
 392             y += columnPref.height;
 393         }
 394         Component view = (viewport == null) ? null : viewport.getView();
 395         if (view != null && view.isVisible() &&
 396                 view.getBaselineResizeBehavior() ==
 397                 Component.BaselineResizeBehavior.CONSTANT_ASCENT) {
 398             Border viewportBorder = scrollpane.getViewportBorder();
 399             if (viewportBorder != null) {
 400                 Insets vpbInsets = viewportBorder.getBorderInsets(scrollpane);
 401                 y += vpbInsets.top;
 402                 height = height - vpbInsets.top - vpbInsets.bottom;
 403                 width = width - vpbInsets.left - vpbInsets.right;
 404             }
 405             if (view.getWidth() > 0 && view.getHeight() > 0) {
 406                 Dimension min = view.getMinimumSize();
 407                 width = Math.max(min.width, view.getWidth());
 408                 height = Math.max(min.height, view.getHeight());
 409             }
 410             if (width > 0 && height > 0) {
 411                 int baseline = view.getBaseline(width, height);
 412                 if (baseline > 0) {
 413                     return y + baseline;
 414                 }
 415             }
 416         }
 417         return -1;
 418     }
 419 
 420     /**
 421      * Returns an enum indicating how the baseline of the component
 422      * changes as the size changes.
 423      *
 424      * @throws NullPointerException {@inheritDoc}
 425      * @see javax.swing.JComponent#getBaseline(int, int)
 426      * @since 1.6
 427      */
 428     public Component.BaselineResizeBehavior getBaselineResizeBehavior(
 429             JComponent c) {
 430         super.getBaselineResizeBehavior(c);
 431         // Baseline is either from the header, in which case it's always
 432         // the same size and therefor can be created as CONSTANT_ASCENT.
 433         // If the header doesn't have a baseline than the baseline will only
 434         // be valid if it's BaselineResizeBehavior is
 435         // CONSTANT_ASCENT, so, return CONSTANT_ASCENT.
 436         return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
 437     }
 438 
 439 
 440     /**
 441      * Listener for viewport events.
 442      */
 443     public class ViewportChangeHandler implements ChangeListener
 444     {
 445 
 446         // NOTE: This class exists only for backward compatability. All
 447         // its functionality has been moved into Handler. If you need to add
 448         // new functionality add it to the Handler, but make sure this
 449         // class calls into the Handler.
 450 
 451         public void stateChanged(ChangeEvent e) {
 452             getHandler().stateChanged(e);
 453         }
 454     }
 455 
 456     protected ChangeListener createViewportChangeListener() {
 457         return getHandler();
 458     }
 459 
 460 
 461     /**
 462      * Horizontal scrollbar listener.
 463      */
 464     public class HSBChangeListener implements ChangeListener
 465     {
 466 
 467         // NOTE: This class exists only for backward compatability. All
 468         // its functionality has been moved into Handler. If you need to add
 469         // new functionality add it to the Handler, but make sure this
 470         // class calls into the Handler.
 471 
 472         public void stateChanged(ChangeEvent e)
 473         {
 474             getHandler().stateChanged(e);
 475         }
 476     }
 477 
 478     /**
 479      * Returns a <code>PropertyChangeListener</code> that will be installed
 480      * on the horizontal <code>JScrollBar</code>.
 481      */
 482     private PropertyChangeListener createHSBPropertyChangeListener() {
 483         return getHandler();
 484     }
 485 
 486     protected ChangeListener createHSBChangeListener() {
 487         return getHandler();
 488     }
 489 
 490 
 491     /**
 492      * Vertical scrollbar listener.
 493      */
 494     public class VSBChangeListener implements ChangeListener
 495     {
 496 
 497         // NOTE: This class exists only for backward compatability. All
 498         // its functionality has been moved into Handler. If you need to add
 499         // new functionality add it to the Handler, but make sure this
 500         // class calls into the Handler.
 501 
 502         public void stateChanged(ChangeEvent e)
 503         {
 504             getHandler().stateChanged(e);
 505         }
 506     }
 507 
 508 
 509     /**
 510      * Returns a <code>PropertyChangeListener</code> that will be installed
 511      * on the vertical <code>JScrollBar</code>.
 512      */
 513     private PropertyChangeListener createVSBPropertyChangeListener() {
 514         return getHandler();
 515     }
 516 
 517     protected ChangeListener createVSBChangeListener() {
 518         return getHandler();
 519     }
 520 
 521     /**
 522      * MouseWheelHandler is an inner class which implements the
 523      * MouseWheelListener interface.  MouseWheelHandler responds to
 524      * MouseWheelEvents by scrolling the JScrollPane appropriately.
 525      * If the scroll pane's
 526      * <code>isWheelScrollingEnabled</code>
 527      * method returns false, no scrolling occurs.
 528      *
 529      * @see javax.swing.JScrollPane#isWheelScrollingEnabled
 530      * @see #createMouseWheelListener
 531      * @see java.awt.event.MouseWheelListener
 532      * @see java.awt.event.MouseWheelEvent
 533      * @since 1.4
 534      */
 535     protected class MouseWheelHandler implements MouseWheelListener {
 536 
 537         // NOTE: This class exists only for backward compatability. All
 538         // its functionality has been moved into Handler. If you need to add
 539         // new functionality add it to the Handler, but make sure this
 540         // class calls into the Handler.
 541 
 542         /**
 543          * Called when the mouse wheel is rotated while over a
 544          * JScrollPane.
 545          *
 546          * @param e     MouseWheelEvent to be handled
 547          * @since 1.4
 548          */
 549         public void mouseWheelMoved(MouseWheelEvent e) {
 550             getHandler().mouseWheelMoved(e);
 551         }
 552     }
 553 
 554     /**
 555      * Creates an instance of MouseWheelListener, which is added to the
 556      * JScrollPane by installUI().  The returned MouseWheelListener is used
 557      * to handle mouse wheel-driven scrolling.
 558      *
 559      * @return      MouseWheelListener which implements wheel-driven scrolling
 560      * @see #installUI
 561      * @see MouseWheelHandler
 562      * @since 1.4
 563      */
 564     protected MouseWheelListener createMouseWheelListener() {
 565         return getHandler();
 566     }
 567 
 568     protected void updateScrollBarDisplayPolicy(PropertyChangeEvent e) {
 569         scrollpane.revalidate();
 570         scrollpane.repaint();
 571     }
 572 
 573 
 574     protected void updateViewport(PropertyChangeEvent e)
 575     {
 576         JViewport oldViewport = (JViewport)(e.getOldValue());
 577         JViewport newViewport = (JViewport)(e.getNewValue());
 578 
 579         if (oldViewport != null) {
 580             oldViewport.removeChangeListener(viewportChangeListener);
 581         }
 582 
 583         if (newViewport != null) {
 584             Point p = newViewport.getViewPosition();
 585             if (scrollpane.getComponentOrientation().isLeftToRight()) {
 586                 p.x = Math.max(p.x, 0);
 587             } else {
 588                 int max = newViewport.getViewSize().width;
 589                 int extent = newViewport.getExtentSize().width;
 590                 if (extent > max) {
 591                     p.x = max - extent;
 592                 } else {
 593                     p.x = Math.max(0, Math.min(max - extent, p.x));
 594                 }
 595             }
 596             p.y = Math.max(p.y, 0);
 597             newViewport.setViewPosition(p);
 598             newViewport.addChangeListener(viewportChangeListener);
 599         }
 600     }
 601 
 602 
 603     protected void updateRowHeader(PropertyChangeEvent e)
 604     {
 605         JViewport newRowHead = (JViewport)(e.getNewValue());
 606         if (newRowHead != null) {
 607             JViewport viewport = scrollpane.getViewport();
 608             Point p = newRowHead.getViewPosition();
 609             p.y = (viewport != null) ? viewport.getViewPosition().y : 0;
 610             newRowHead.setViewPosition(p);
 611         }
 612     }
 613 
 614 
 615     protected void updateColumnHeader(PropertyChangeEvent e)
 616     {
 617         JViewport newColHead = (JViewport)(e.getNewValue());
 618         if (newColHead != null) {
 619             JViewport viewport = scrollpane.getViewport();
 620             Point p = newColHead.getViewPosition();
 621             if (viewport == null) {
 622                 p.x = 0;
 623             } else {
 624                 if (scrollpane.getComponentOrientation().isLeftToRight()) {
 625                     p.x = viewport.getViewPosition().x;
 626                 } else {
 627                     p.x = Math.max(0, viewport.getViewPosition().x);
 628                 }
 629             }
 630             newColHead.setViewPosition(p);
 631             scrollpane.add(newColHead, COLUMN_HEADER);
 632         }
 633     }
 634 
 635     private void updateHorizontalScrollBar(PropertyChangeEvent pce) {
 636         updateScrollBar(pce, hsbChangeListener, hsbPropertyChangeListener);
 637     }
 638 
 639     private void updateVerticalScrollBar(PropertyChangeEvent pce) {
 640         updateScrollBar(pce, vsbChangeListener, vsbPropertyChangeListener);
 641     }
 642 
 643     private void updateScrollBar(PropertyChangeEvent pce, ChangeListener cl,
 644                                  PropertyChangeListener pcl) {
 645         JScrollBar sb = (JScrollBar)pce.getOldValue();
 646         if (sb != null) {
 647             if (cl != null) {
 648                 sb.getModel().removeChangeListener(cl);
 649             }
 650             if (pcl != null) {
 651                 sb.removePropertyChangeListener(pcl);
 652             }
 653         }
 654         sb = (JScrollBar)pce.getNewValue();
 655         if (sb != null) {
 656             if (cl != null) {
 657                 sb.getModel().addChangeListener(cl);
 658             }
 659             if (pcl != null) {
 660                 sb.addPropertyChangeListener(pcl);
 661             }
 662         }
 663     }
 664 
 665     public class PropertyChangeHandler implements PropertyChangeListener
 666     {
 667 
 668         // NOTE: This class exists only for backward compatability. All
 669         // its functionality has been moved into Handler. If you need to add
 670         // new functionality add it to the Handler, but make sure this
 671         // class calls into the Handler.
 672 
 673         public void propertyChange(PropertyChangeEvent e)
 674         {
 675             getHandler().propertyChange(e);
 676         }
 677     }
 678 
 679 
 680 
 681     /**
 682      * Creates an instance of PropertyChangeListener that's added to
 683      * the JScrollPane by installUI().  Subclasses can override this method
 684      * to return a custom PropertyChangeListener, e.g.
 685      * <pre>
 686      * class MyScrollPaneUI extends BasicScrollPaneUI {
 687      *    protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
 688      *        return new MyPropertyChangeListener();
 689      *    }
 690      *    public class MyPropertyChangeListener extends PropertyChangeListener {
 691      *        public void propertyChange(PropertyChangeEvent e) {
 692      *            if (e.getPropertyName().equals("viewport")) {
 693      *                // do some extra work when the viewport changes
 694      *            }
 695      *            super.propertyChange(e);
 696      *        }
 697      *    }
 698      * }
 699      * </pre>
 700      *
 701      * @see java.beans.PropertyChangeListener
 702      * @see #installUI
 703      */
 704     protected PropertyChangeListener createPropertyChangeListener() {
 705         return getHandler();
 706     }
 707 
 708 
 709     private static class Actions extends UIAction {
 710         private static final String SCROLL_UP = "scrollUp";
 711         private static final String SCROLL_DOWN = "scrollDown";
 712         private static final String SCROLL_HOME = "scrollHome";
 713         private static final String SCROLL_END = "scrollEnd";
 714         private static final String UNIT_SCROLL_UP = "unitScrollUp";
 715         private static final String UNIT_SCROLL_DOWN = "unitScrollDown";
 716         private static final String SCROLL_LEFT = "scrollLeft";
 717         private static final String SCROLL_RIGHT = "scrollRight";
 718         private static final String UNIT_SCROLL_LEFT = "unitScrollLeft";
 719         private static final String UNIT_SCROLL_RIGHT = "unitScrollRight";
 720 
 721 
 722         Actions(String key) {
 723             super(key);
 724         }
 725 
 726         public void actionPerformed(ActionEvent e) {
 727             JScrollPane scrollPane = (JScrollPane)e.getSource();
 728             boolean ltr = scrollPane.getComponentOrientation().isLeftToRight();
 729             String key = getName();
 730 
 731             if (key == SCROLL_UP) {
 732                 scroll(scrollPane, SwingConstants.VERTICAL, -1, true);
 733             }
 734             else if (key == SCROLL_DOWN) {
 735                 scroll(scrollPane, SwingConstants.VERTICAL, 1, true);
 736             }
 737             else if (key == SCROLL_HOME) {
 738                 scrollHome(scrollPane);
 739             }
 740             else if (key == SCROLL_END) {
 741                 scrollEnd(scrollPane);
 742             }
 743             else if (key == UNIT_SCROLL_UP) {
 744                 scroll(scrollPane, SwingConstants.VERTICAL, -1, false);
 745             }
 746             else if (key == UNIT_SCROLL_DOWN) {
 747                 scroll(scrollPane, SwingConstants.VERTICAL, 1, false);
 748             }
 749             else if (key == SCROLL_LEFT) {
 750                 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1,
 751                        true);
 752             }
 753             else if (key == SCROLL_RIGHT) {
 754                 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1,
 755                        true);
 756             }
 757             else if (key == UNIT_SCROLL_LEFT) {
 758                 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1,
 759                        false);
 760             }
 761             else if (key == UNIT_SCROLL_RIGHT) {
 762                 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1,
 763                        false);
 764             }
 765         }
 766 
 767         private void scrollEnd(JScrollPane scrollpane) {
 768             JViewport vp = scrollpane.getViewport();
 769             Component view;
 770             if (vp != null && (view = vp.getView()) != null) {
 771                 Rectangle visRect = vp.getViewRect();
 772                 Rectangle bounds = view.getBounds();
 773                 if (scrollpane.getComponentOrientation().isLeftToRight()) {
 774                     vp.setViewPosition(new Point(bounds.width - visRect.width,
 775                                              bounds.height - visRect.height));
 776                 } else {
 777                     vp.setViewPosition(new Point(0,
 778                                              bounds.height - visRect.height));
 779                 }
 780             }
 781         }
 782 
 783         private void scrollHome(JScrollPane scrollpane) {
 784             JViewport vp = scrollpane.getViewport();
 785             Component view;
 786             if (vp != null && (view = vp.getView()) != null) {
 787                 if (scrollpane.getComponentOrientation().isLeftToRight()) {
 788                     vp.setViewPosition(new Point(0, 0));
 789                 } else {
 790                     Rectangle visRect = vp.getViewRect();
 791                     Rectangle bounds = view.getBounds();
 792                     vp.setViewPosition(new Point(bounds.width - visRect.width, 0));
 793                 }
 794             }
 795         }
 796 
 797         private void scroll(JScrollPane scrollpane, int orientation,
 798                             int direction, boolean block) {
 799             JViewport vp = scrollpane.getViewport();
 800             Component view;
 801             if (vp != null && (view = vp.getView()) != null) {
 802                 Rectangle visRect = vp.getViewRect();
 803                 Dimension vSize = view.getSize();
 804                 int amount;
 805 
 806                 if (view instanceof Scrollable) {
 807                     if (block) {
 808                         amount = ((Scrollable)view).getScrollableBlockIncrement
 809                                  (visRect, orientation, direction);
 810                     }
 811                     else {
 812                         amount = ((Scrollable)view).getScrollableUnitIncrement
 813                                  (visRect, orientation, direction);
 814                     }
 815                 }
 816                 else {
 817                     if (block) {
 818                         if (orientation == SwingConstants.VERTICAL) {
 819                             amount = visRect.height;
 820                         }
 821                         else {
 822                             amount = visRect.width;
 823                         }
 824                     }
 825                     else {
 826                         amount = 10;
 827                     }
 828                 }
 829                 if (orientation == SwingConstants.VERTICAL) {
 830                     visRect.y += (amount * direction);
 831                     if ((visRect.y + visRect.height) > vSize.height) {
 832                         visRect.y = Math.max(0, vSize.height - visRect.height);
 833                     }
 834                     else if (visRect.y < 0) {
 835                         visRect.y = 0;
 836                     }
 837                 }
 838                 else {
 839                     if (scrollpane.getComponentOrientation().isLeftToRight()) {
 840                         visRect.x += (amount * direction);
 841                         if ((visRect.x + visRect.width) > vSize.width) {
 842                             visRect.x = Math.max(0, vSize.width - visRect.width);
 843                         } else if (visRect.x < 0) {
 844                             visRect.x = 0;
 845                         }
 846                     } else {
 847                         visRect.x -= (amount * direction);
 848                         if (visRect.width > vSize.width) {
 849                             visRect.x = vSize.width - visRect.width;
 850                         } else {
 851                             visRect.x = Math.max(0, Math.min(vSize.width - visRect.width, visRect.x));
 852                         }
 853                     }
 854                 }
 855                 vp.setViewPosition(visRect.getLocation());
 856             }
 857         }
 858     }
 859 
 860 
 861     class Handler implements ChangeListener, PropertyChangeListener, MouseWheelListener {
 862         //
 863         // MouseWheelListener
 864         //
 865         public void mouseWheelMoved(MouseWheelEvent e) {
 866             if (scrollpane.isWheelScrollingEnabled() &&
 867                 e.getWheelRotation() != 0) {
 868                 JScrollBar toScroll = scrollpane.getVerticalScrollBar();
 869                 int direction = e.getWheelRotation() < 0 ? -1 : 1;
 870                 int orientation = SwingConstants.VERTICAL;
 871 
 872                 // find which scrollbar to scroll, or return if none
 873                 if (toScroll == null || !toScroll.isVisible()) {
 874                     toScroll = scrollpane.getHorizontalScrollBar();
 875                     if (toScroll == null || !toScroll.isVisible()) {
 876                         return;
 877                     }
 878                     orientation = SwingConstants.HORIZONTAL;
 879                 }
 880 
 881                 e.consume();
 882 
 883                 if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
 884                     JViewport vp = scrollpane.getViewport();
 885                     if (vp == null) { return; }
 886                     Component comp = vp.getView();
 887                     int units = Math.abs(e.getUnitsToScroll());
 888 
 889                     // When the scrolling speed is set to maximum, it's possible
 890                     // for a single wheel click to scroll by more units than
 891                     // will fit in the visible area.  This makes it
 892                     // hard/impossible to get to certain parts of the scrolling
 893                     // Component with the wheel.  To make for more accurate
 894                     // low-speed scrolling, we limit scrolling to the block
 895                     // increment if the wheel was only rotated one click.
 896                     boolean limitScroll = Math.abs(e.getWheelRotation()) == 1;
 897 
 898                     // Check if we should use the visibleRect trick
 899                     Object fastWheelScroll = toScroll.getClientProperty(
 900                                                "JScrollBar.fastWheelScrolling");
 901                     if (Boolean.TRUE == fastWheelScroll &&
 902                         comp instanceof Scrollable) {
 903                         // 5078454: Under maximum acceleration, we may scroll
 904                         // by many 100s of units in ~1 second.
 905                         //
 906                         // BasicScrollBarUI.scrollByUnits() can bog down the EDT
 907                         // with repaints in this situation.  However, the
 908                         // Scrollable interface allows us to pass in an
 909                         // arbitrary visibleRect.  This allows us to accurately
 910                         // calculate the total scroll amount, and then update
 911                         // the GUI once.  This technique provides much faster
 912                         // accelerated wheel scrolling.
 913                         Scrollable scrollComp = (Scrollable) comp;
 914                         Rectangle viewRect = vp.getViewRect();
 915                         int startingX = viewRect.x;
 916                         boolean leftToRight =
 917                                  comp.getComponentOrientation().isLeftToRight();
 918                         int scrollMin = toScroll.getMinimum();
 919                         int scrollMax = toScroll.getMaximum() -
 920                                         toScroll.getModel().getExtent();
 921 
 922                         if (limitScroll) {
 923                             int blockIncr =
 924                                 scrollComp.getScrollableBlockIncrement(viewRect,
 925                                                                     orientation,
 926                                                                     direction);
 927                             if (direction < 0) {
 928                                 scrollMin = Math.max(scrollMin,
 929                                                toScroll.getValue() - blockIncr);
 930                             }
 931                             else {
 932                                 scrollMax = Math.min(scrollMax,
 933                                                toScroll.getValue() + blockIncr);
 934                             }
 935                         }
 936 
 937                         for (int i = 0; i < units; i++) {
 938                             int unitIncr =
 939                                 scrollComp.getScrollableUnitIncrement(viewRect,
 940                                                         orientation, direction);
 941                             // Modify the visible rect for the next unit, and
 942                             // check to see if we're at the end already.
 943                             if (orientation == SwingConstants.VERTICAL) {
 944                                 if (direction < 0) {
 945                                     viewRect.y -= unitIncr;
 946                                     if (viewRect.y <= scrollMin) {
 947                                         viewRect.y = scrollMin;
 948                                         break;
 949                                     }
 950                                 }
 951                                 else { // (direction > 0
 952                                     viewRect.y += unitIncr;
 953                                     if (viewRect.y >= scrollMax) {
 954                                         viewRect.y = scrollMax;
 955                                         break;
 956                                     }
 957                                 }
 958                             }
 959                             else {
 960                                 // Scroll left
 961                                 if ((leftToRight && direction < 0) ||
 962                                     (!leftToRight && direction > 0)) {
 963                                     viewRect.x -= unitIncr;
 964                                     if (leftToRight) {
 965                                         if (viewRect.x < scrollMin) {
 966                                             viewRect.x = scrollMin;
 967                                             break;
 968                                         }
 969                                     }
 970                                 }
 971                                 // Scroll right
 972                                 else if ((leftToRight && direction > 0) ||
 973                                     (!leftToRight && direction < 0)) {
 974                                     viewRect.x += unitIncr;
 975                                     if (leftToRight) {
 976                                         if (viewRect.x > scrollMax) {
 977                                             viewRect.x = scrollMax;
 978                                             break;
 979                                         }
 980                                     }
 981                                 }
 982                                 else {
 983                                     assert false : "Non-sensical ComponentOrientation / scroll direction";
 984                                 }
 985                             }
 986                         }
 987                         // Set the final view position on the ScrollBar
 988                         if (orientation == SwingConstants.VERTICAL) {
 989                             toScroll.setValue(viewRect.y);
 990                         }
 991                         else {
 992                             if (leftToRight) {
 993                                 toScroll.setValue(viewRect.x);
 994                             }
 995                             else {
 996                                 // rightToLeft scrollbars are oriented with
 997                                 // minValue on the right and maxValue on the
 998                                 // left.
 999                                 int newPos = toScroll.getValue() -
1000                                                        (viewRect.x - startingX);
1001                                 if (newPos < scrollMin) {
1002                                     newPos = scrollMin;
1003                                 }
1004                                 else if (newPos > scrollMax) {
1005                                     newPos = scrollMax;
1006                                 }
1007                                 toScroll.setValue(newPos);
1008                             }
1009                         }
1010                     }
1011                     else {
1012                         // Viewport's view is not a Scrollable, or fast wheel
1013                         // scrolling is not enabled.
1014                         BasicScrollBarUI.scrollByUnits(toScroll, direction,
1015                                                        units, limitScroll);
1016                     }
1017                 }
1018                 else if (e.getScrollType() ==
1019                          MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
1020                     BasicScrollBarUI.scrollByBlock(toScroll, direction);
1021                 }
1022             }
1023         }
1024 
1025         //
1026         // ChangeListener: This is added to the vieport, and hsb/vsb models.
1027         //
1028         public void stateChanged(ChangeEvent e) {
1029             JViewport viewport = scrollpane.getViewport();
1030 
1031             if (viewport != null) {
1032                 if (e.getSource() == viewport) {
1033                     syncScrollPaneWithViewport();
1034                 }
1035                 else {
1036                     JScrollBar hsb = scrollpane.getHorizontalScrollBar();
1037                     if (hsb != null && e.getSource() == hsb.getModel()) {
1038                         hsbStateChanged(viewport, e);
1039                     }
1040                     else {
1041                         JScrollBar vsb = scrollpane.getVerticalScrollBar();
1042                         if (vsb != null && e.getSource() == vsb.getModel()) {
1043                             vsbStateChanged(viewport, e);
1044                         }
1045                     }
1046                 }
1047             }
1048         }
1049 
1050         private void vsbStateChanged(JViewport viewport, ChangeEvent e) {
1051             BoundedRangeModel model = (BoundedRangeModel)(e.getSource());
1052             Point p = viewport.getViewPosition();
1053             p.y = model.getValue();
1054             viewport.setViewPosition(p);
1055         }
1056 
1057         private void hsbStateChanged(JViewport viewport, ChangeEvent e) {
1058             BoundedRangeModel model = (BoundedRangeModel)(e.getSource());
1059             Point p = viewport.getViewPosition();
1060             int value = model.getValue();
1061             if (scrollpane.getComponentOrientation().isLeftToRight()) {
1062                 p.x = value;
1063             } else {
1064                 int max = viewport.getViewSize().width;
1065                 int extent = viewport.getExtentSize().width;
1066                 int oldX = p.x;
1067 
1068                 /* Set new X coordinate based on "value".
1069                  */
1070                 p.x = max - extent - value;
1071 
1072                 /* If setValue() was called before "extent" was fixed,
1073                  * turn setValueCalled flag on.
1074                  */
1075                 if ((extent == 0) && (value != 0) && (oldX == max)) {
1076                     setValueCalled = true;
1077                 } else {
1078                     /* When a pane without a horizontal scroll bar was
1079                      * reduced and the bar appeared, the viewport should
1080                      * show the right side of the view.
1081                      */
1082                     if ((extent != 0) && (oldX < 0) && (p.x == 0)) {
1083                         p.x += value;
1084                     }
1085                 }
1086             }
1087             viewport.setViewPosition(p);
1088         }
1089 
1090         //
1091         // PropertyChangeListener: This is installed on both the JScrollPane
1092         // and the horizontal/vertical scrollbars.
1093         //
1094 
1095         // Listens for changes in the model property and reinstalls the
1096         // horizontal/vertical PropertyChangeListeners.
1097         public void propertyChange(PropertyChangeEvent e) {
1098             if (e.getSource() == scrollpane) {
1099                 scrollPanePropertyChange(e);
1100             }
1101             else {
1102                 sbPropertyChange(e);
1103             }
1104         }
1105 
1106         private void scrollPanePropertyChange(PropertyChangeEvent e) {
1107             String propertyName = e.getPropertyName();
1108 
1109             if (propertyName == "verticalScrollBarDisplayPolicy") {
1110                 updateScrollBarDisplayPolicy(e);
1111             }
1112             else if (propertyName == "horizontalScrollBarDisplayPolicy") {
1113                 updateScrollBarDisplayPolicy(e);
1114             }
1115             else if (propertyName == "viewport") {
1116                 updateViewport(e);
1117             }
1118             else if (propertyName == "rowHeader") {
1119                 updateRowHeader(e);
1120             }
1121             else if (propertyName == "columnHeader") {
1122                 updateColumnHeader(e);
1123             }
1124             else if (propertyName == "verticalScrollBar") {
1125                 updateVerticalScrollBar(e);
1126             }
1127             else if (propertyName == "horizontalScrollBar") {
1128                 updateHorizontalScrollBar(e);
1129             }
1130             else if (propertyName == "componentOrientation") {
1131                 scrollpane.revalidate();
1132                 scrollpane.repaint();
1133             }
1134         }
1135 
1136         // PropertyChangeListener for the horizontal and vertical scrollbars.
1137         private void sbPropertyChange(PropertyChangeEvent e) {
1138             String propertyName = e.getPropertyName();
1139             Object source = e.getSource();
1140 
1141             if ("model" == propertyName) {
1142                 JScrollBar sb = scrollpane.getVerticalScrollBar();
1143                 BoundedRangeModel oldModel = (BoundedRangeModel)e.
1144                                      getOldValue();
1145                 ChangeListener cl = null;
1146 
1147                 if (source == sb) {
1148                     cl = vsbChangeListener;
1149                 }
1150                 else if (source == scrollpane.getHorizontalScrollBar()) {
1151                     sb = scrollpane.getHorizontalScrollBar();
1152                     cl = hsbChangeListener;
1153                 }
1154                 if (cl != null) {
1155                     if (oldModel != null) {
1156                         oldModel.removeChangeListener(cl);
1157                     }
1158                     if (sb.getModel() != null) {
1159                         sb.getModel().addChangeListener(cl);
1160                     }
1161                 }
1162             }
1163             else if ("componentOrientation" == propertyName) {
1164                 if (source == scrollpane.getHorizontalScrollBar()) {
1165                     JScrollBar hsb = scrollpane.getHorizontalScrollBar();
1166                     JViewport viewport = scrollpane.getViewport();
1167                     Point p = viewport.getViewPosition();
1168                     if (scrollpane.getComponentOrientation().isLeftToRight()) {
1169                         p.x = hsb.getValue();
1170                     } else {
1171                         p.x = viewport.getViewSize().width - viewport.getExtentSize().width - hsb.getValue();
1172                     }
1173                     viewport.setViewPosition(p);
1174                 }
1175             }
1176         }
1177     }
1178 }