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