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     public class PropertyChangeHandler implements PropertyChangeListener
 750     {
 751 
 752         // NOTE: This class exists only for backward compatibility. All
 753         // its functionality has been moved into Handler. If you need to add
 754         // new functionality add it to the Handler, but make sure this
 755         // class calls into the Handler.
 756 
 757         public void propertyChange(PropertyChangeEvent e)
 758         {
 759             getHandler().propertyChange(e);
 760         }
 761     }
 762 
 763 
 764 
 765     /**
 766      * Creates an instance of {@code PropertyChangeListener} that's added to
 767      * the {@code JScrollPane} by {@code installUI()}. Subclasses can override
 768      * this method to return a custom {@code PropertyChangeListener}, e.g.
 769      * <pre>
 770      * class MyScrollPaneUI extends BasicScrollPaneUI {
 771      *    protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
 772      *        return new MyPropertyChangeListener();
 773      *    }
 774      *    public class MyPropertyChangeListener extends PropertyChangeListener {
 775      *        public void propertyChange(PropertyChangeEvent e) {
 776      *            if (e.getPropertyName().equals("viewport")) {
 777      *                // do some extra work when the viewport changes
 778      *            }
 779      *            super.propertyChange(e);
 780      *        }
 781      *    }
 782      * }
 783      * </pre>
 784      *
 785      * @return an instance of {@code PropertyChangeListener}
 786      *
 787      * @see java.beans.PropertyChangeListener
 788      * @see #installUI
 789      */
 790     protected PropertyChangeListener createPropertyChangeListener() {
 791         return getHandler();
 792     }
 793 
 794 
 795     private static class Actions extends UIAction {
 796         private static final String SCROLL_UP = "scrollUp";
 797         private static final String SCROLL_DOWN = "scrollDown";
 798         private static final String SCROLL_HOME = "scrollHome";
 799         private static final String SCROLL_END = "scrollEnd";
 800         private static final String UNIT_SCROLL_UP = "unitScrollUp";
 801         private static final String UNIT_SCROLL_DOWN = "unitScrollDown";
 802         private static final String SCROLL_LEFT = "scrollLeft";
 803         private static final String SCROLL_RIGHT = "scrollRight";
 804         private static final String UNIT_SCROLL_LEFT = "unitScrollLeft";
 805         private static final String UNIT_SCROLL_RIGHT = "unitScrollRight";
 806 
 807 
 808         Actions(String key) {
 809             super(key);
 810         }
 811 
 812         public void actionPerformed(ActionEvent e) {
 813             JScrollPane scrollPane = (JScrollPane)e.getSource();
 814             boolean ltr = scrollPane.getComponentOrientation().isLeftToRight();
 815             String key = getName();
 816 
 817             if (key == SCROLL_UP) {
 818                 scroll(scrollPane, SwingConstants.VERTICAL, -1, true);
 819             }
 820             else if (key == SCROLL_DOWN) {
 821                 scroll(scrollPane, SwingConstants.VERTICAL, 1, true);
 822             }
 823             else if (key == SCROLL_HOME) {
 824                 scrollHome(scrollPane);
 825             }
 826             else if (key == SCROLL_END) {
 827                 scrollEnd(scrollPane);
 828             }
 829             else if (key == UNIT_SCROLL_UP) {
 830                 scroll(scrollPane, SwingConstants.VERTICAL, -1, false);
 831             }
 832             else if (key == UNIT_SCROLL_DOWN) {
 833                 scroll(scrollPane, SwingConstants.VERTICAL, 1, false);
 834             }
 835             else if (key == SCROLL_LEFT) {
 836                 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1,
 837                        true);
 838             }
 839             else if (key == SCROLL_RIGHT) {
 840                 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1,
 841                        true);
 842             }
 843             else if (key == UNIT_SCROLL_LEFT) {
 844                 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1,
 845                        false);
 846             }
 847             else if (key == UNIT_SCROLL_RIGHT) {
 848                 scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1,
 849                        false);
 850             }
 851         }
 852 
 853         private void scrollEnd(JScrollPane scrollpane) {
 854             JViewport vp = scrollpane.getViewport();
 855             Component view;
 856             if (vp != null && (view = vp.getView()) != null) {
 857                 Rectangle visRect = vp.getViewRect();
 858                 Rectangle bounds = view.getBounds();
 859                 if (scrollpane.getComponentOrientation().isLeftToRight()) {
 860                     vp.setViewPosition(new Point(bounds.width - visRect.width,
 861                                              bounds.height - visRect.height));
 862                 } else {
 863                     vp.setViewPosition(new Point(0,
 864                                              bounds.height - visRect.height));
 865                 }
 866             }
 867         }
 868 
 869         private void scrollHome(JScrollPane scrollpane) {
 870             JViewport vp = scrollpane.getViewport();
 871             Component view;
 872             if (vp != null && (view = vp.getView()) != null) {
 873                 if (scrollpane.getComponentOrientation().isLeftToRight()) {
 874                     vp.setViewPosition(new Point(0, 0));
 875                 } else {
 876                     Rectangle visRect = vp.getViewRect();
 877                     Rectangle bounds = view.getBounds();
 878                     vp.setViewPosition(new Point(bounds.width - visRect.width, 0));
 879                 }
 880             }
 881         }
 882 
 883         private void scroll(JScrollPane scrollpane, int orientation,
 884                             int direction, boolean block) {
 885             JViewport vp = scrollpane.getViewport();
 886             Component view;
 887             if (vp != null && (view = vp.getView()) != null) {
 888                 Rectangle visRect = vp.getViewRect();
 889                 Dimension vSize = view.getSize();
 890                 int amount;
 891 
 892                 if (view instanceof Scrollable) {
 893                     if (block) {
 894                         amount = ((Scrollable)view).getScrollableBlockIncrement
 895                                  (visRect, orientation, direction);
 896                     }
 897                     else {
 898                         amount = ((Scrollable)view).getScrollableUnitIncrement
 899                                  (visRect, orientation, direction);
 900                     }
 901                 }
 902                 else {
 903                     if (block) {
 904                         if (orientation == SwingConstants.VERTICAL) {
 905                             amount = visRect.height;
 906                         }
 907                         else {
 908                             amount = visRect.width;
 909                         }
 910                     }
 911                     else {
 912                         amount = 10;
 913                     }
 914                 }
 915                 if (orientation == SwingConstants.VERTICAL) {
 916                     visRect.y += (amount * direction);
 917                     if ((visRect.y + visRect.height) > vSize.height) {
 918                         visRect.y = Math.max(0, vSize.height - visRect.height);
 919                     }
 920                     else if (visRect.y < 0) {
 921                         visRect.y = 0;
 922                     }
 923                 }
 924                 else {
 925                     if (scrollpane.getComponentOrientation().isLeftToRight()) {
 926                         visRect.x += (amount * direction);
 927                         if ((visRect.x + visRect.width) > vSize.width) {
 928                             visRect.x = Math.max(0, vSize.width - visRect.width);
 929                         } else if (visRect.x < 0) {
 930                             visRect.x = 0;
 931                         }
 932                     } else {
 933                         visRect.x -= (amount * direction);
 934                         if (visRect.width > vSize.width) {
 935                             visRect.x = vSize.width - visRect.width;
 936                         } else {
 937                             visRect.x = Math.max(0, Math.min(vSize.width - visRect.width, visRect.x));
 938                         }
 939                     }
 940                 }
 941                 vp.setViewPosition(visRect.getLocation());
 942             }
 943         }
 944     }
 945 
 946 
 947     class Handler implements ChangeListener, PropertyChangeListener, MouseWheelListener {
 948         //
 949         // MouseWheelListener
 950         //
 951         public void mouseWheelMoved(MouseWheelEvent e) {
 952             if (scrollpane.isWheelScrollingEnabled() &&
 953                 e.getWheelRotation() != 0) {
 954                 JScrollBar toScroll = scrollpane.getVerticalScrollBar();
 955                 int direction = e.getWheelRotation() < 0 ? -1 : 1;
 956                 int orientation = SwingConstants.VERTICAL;
 957 
 958                 // find which scrollbar to scroll, or return if none
 959                 if (toScroll == null || !toScroll.isVisible()) {
 960                     toScroll = scrollpane.getHorizontalScrollBar();
 961                     if (toScroll == null || !toScroll.isVisible()) {
 962                         return;
 963                     }
 964                     orientation = SwingConstants.HORIZONTAL;
 965                 } else if(e.isShiftDown()){
 966                     JScrollBar hScroll = scrollpane.getHorizontalScrollBar();
 967                     if (hScroll != null && hScroll.isVisible()) {
 968                         toScroll = hScroll;
 969                         orientation = SwingConstants.HORIZONTAL;
 970                     }
 971                 }
 972 
 973                 e.consume();
 974 
 975                 if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
 976                     JViewport vp = scrollpane.getViewport();
 977                     if (vp == null) { return; }
 978                     Component comp = vp.getView();
 979                     int units = Math.abs(e.getUnitsToScroll());
 980 
 981                     // When the scrolling speed is set to maximum, it's possible
 982                     // for a single wheel click to scroll by more units than
 983                     // will fit in the visible area.  This makes it
 984                     // hard/impossible to get to certain parts of the scrolling
 985                     // Component with the wheel.  To make for more accurate
 986                     // low-speed scrolling, we limit scrolling to the block
 987                     // increment if the wheel was only rotated one click.
 988                     boolean limitScroll = Math.abs(e.getWheelRotation()) == 1;
 989 
 990                     // Check if we should use the visibleRect trick
 991                     Object fastWheelScroll = toScroll.getClientProperty(
 992                                                "JScrollBar.fastWheelScrolling");
 993                     if (Boolean.TRUE == fastWheelScroll &&
 994                         comp instanceof Scrollable) {
 995                         // 5078454: Under maximum acceleration, we may scroll
 996                         // by many 100s of units in ~1 second.
 997                         //
 998                         // BasicScrollBarUI.scrollByUnits() can bog down the EDT
 999                         // with repaints in this situation.  However, the
1000                         // Scrollable interface allows us to pass in an
1001                         // arbitrary visibleRect.  This allows us to accurately
1002                         // calculate the total scroll amount, and then update
1003                         // the GUI once.  This technique provides much faster
1004                         // accelerated wheel scrolling.
1005                         Scrollable scrollComp = (Scrollable) comp;
1006                         Rectangle viewRect = vp.getViewRect();
1007                         int startingX = viewRect.x;
1008                         boolean leftToRight =
1009                                  comp.getComponentOrientation().isLeftToRight();
1010                         int scrollMin = toScroll.getMinimum();
1011                         int scrollMax = toScroll.getMaximum() -
1012                                         toScroll.getModel().getExtent();
1013 
1014                         if (limitScroll) {
1015                             int blockIncr =
1016                                 scrollComp.getScrollableBlockIncrement(viewRect,
1017                                                                     orientation,
1018                                                                     direction);
1019                             if (direction < 0) {
1020                                 scrollMin = Math.max(scrollMin,
1021                                                toScroll.getValue() - blockIncr);
1022                             }
1023                             else {
1024                                 scrollMax = Math.min(scrollMax,
1025                                                toScroll.getValue() + blockIncr);
1026                             }
1027                         }
1028 
1029                         for (int i = 0; i < units; i++) {
1030                             int unitIncr =
1031                                 scrollComp.getScrollableUnitIncrement(viewRect,
1032                                                         orientation, direction);
1033                             // Modify the visible rect for the next unit, and
1034                             // check to see if we're at the end already.
1035                             if (orientation == SwingConstants.VERTICAL) {
1036                                 if (direction < 0) {
1037                                     viewRect.y -= unitIncr;
1038                                     if (viewRect.y <= scrollMin) {
1039                                         viewRect.y = scrollMin;
1040                                         break;
1041                                     }
1042                                 }
1043                                 else { // (direction > 0
1044                                     viewRect.y += unitIncr;
1045                                     if (viewRect.y >= scrollMax) {
1046                                         viewRect.y = scrollMax;
1047                                         break;
1048                                     }
1049                                 }
1050                             }
1051                             else {
1052                                 // Scroll left
1053                                 if ((leftToRight && direction < 0) ||
1054                                     (!leftToRight && direction > 0)) {
1055                                     viewRect.x -= unitIncr;
1056                                     if (leftToRight) {
1057                                         if (viewRect.x < scrollMin) {
1058                                             viewRect.x = scrollMin;
1059                                             break;
1060                                         }
1061                                     }
1062                                 }
1063                                 // Scroll right
1064                                 else if ((leftToRight && direction > 0) ||
1065                                     (!leftToRight && direction < 0)) {
1066                                     viewRect.x += unitIncr;
1067                                     if (leftToRight) {
1068                                         if (viewRect.x > scrollMax) {
1069                                             viewRect.x = scrollMax;
1070                                             break;
1071                                         }
1072                                     }
1073                                 }
1074                                 else {
1075                                     assert false : "Non-sensical ComponentOrientation / scroll direction";
1076                                 }
1077                             }
1078                         }
1079                         // Set the final view position on the ScrollBar
1080                         if (orientation == SwingConstants.VERTICAL) {
1081                             toScroll.setValue(viewRect.y);
1082                         }
1083                         else {
1084                             if (leftToRight) {
1085                                 toScroll.setValue(viewRect.x);
1086                             }
1087                             else {
1088                                 // rightToLeft scrollbars are oriented with
1089                                 // minValue on the right and maxValue on the
1090                                 // left.
1091                                 int newPos = toScroll.getValue() -
1092                                                        (viewRect.x - startingX);
1093                                 if (newPos < scrollMin) {
1094                                     newPos = scrollMin;
1095                                 }
1096                                 else if (newPos > scrollMax) {
1097                                     newPos = scrollMax;
1098                                 }
1099                                 toScroll.setValue(newPos);
1100                             }
1101                         }
1102                     }
1103                     else {
1104                         // Viewport's view is not a Scrollable, or fast wheel
1105                         // scrolling is not enabled.
1106                         BasicScrollBarUI.scrollByUnits(toScroll, direction,
1107                                                        units, limitScroll);
1108                     }
1109                 }
1110                 else if (e.getScrollType() ==
1111                          MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
1112                     BasicScrollBarUI.scrollByBlock(toScroll, direction);
1113                 }
1114             }
1115         }
1116 
1117         //
1118         // ChangeListener: This is added to the vieport, and hsb/vsb models.
1119         //
1120         public void stateChanged(ChangeEvent e) {
1121             JViewport viewport = scrollpane.getViewport();
1122 
1123             if (viewport != null) {
1124                 if (e.getSource() == viewport) {
1125                     syncScrollPaneWithViewport();
1126                 }
1127                 else {
1128                     JScrollBar hsb = scrollpane.getHorizontalScrollBar();
1129                     if (hsb != null && e.getSource() == hsb.getModel()) {
1130                         hsbStateChanged(viewport, e);
1131                     }
1132                     else {
1133                         JScrollBar vsb = scrollpane.getVerticalScrollBar();
1134                         if (vsb != null && e.getSource() == vsb.getModel()) {
1135                             vsbStateChanged(viewport, e);
1136                         }
1137                     }
1138                 }
1139             }
1140         }
1141 
1142         private void vsbStateChanged(JViewport viewport, ChangeEvent e) {
1143             BoundedRangeModel model = (BoundedRangeModel)(e.getSource());
1144             Point p = viewport.getViewPosition();
1145             p.y = model.getValue();
1146             viewport.setViewPosition(p);
1147         }
1148 
1149         private void hsbStateChanged(JViewport viewport, ChangeEvent e) {
1150             BoundedRangeModel model = (BoundedRangeModel)(e.getSource());
1151             Point p = viewport.getViewPosition();
1152             int value = model.getValue();
1153             if (scrollpane.getComponentOrientation().isLeftToRight()) {
1154                 p.x = value;
1155             } else {
1156                 int max = viewport.getViewSize().width;
1157                 int extent = viewport.getExtentSize().width;
1158                 int oldX = p.x;
1159 
1160                 /* Set new X coordinate based on "value".
1161                  */
1162                 p.x = max - extent - value;
1163 
1164                 /* If setValue() was called before "extent" was fixed,
1165                  * turn setValueCalled flag on.
1166                  */
1167                 if ((extent == 0) && (value != 0) && (oldX == max)) {
1168                     setValueCalled = true;
1169                 } else {
1170                     /* When a pane without a horizontal scroll bar was
1171                      * reduced and the bar appeared, the viewport should
1172                      * show the right side of the view.
1173                      */
1174                     if ((extent != 0) && (oldX < 0) && (p.x == 0)) {
1175                         p.x += value;
1176                     }
1177                 }
1178             }
1179             viewport.setViewPosition(p);
1180         }
1181 
1182         //
1183         // PropertyChangeListener: This is installed on both the JScrollPane
1184         // and the horizontal/vertical scrollbars.
1185         //
1186 
1187         // Listens for changes in the model property and reinstalls the
1188         // horizontal/vertical PropertyChangeListeners.
1189         public void propertyChange(PropertyChangeEvent e) {
1190             if (e.getSource() == scrollpane) {
1191                 scrollPanePropertyChange(e);
1192             }
1193             else {
1194                 sbPropertyChange(e);
1195             }
1196         }
1197 
1198         private void scrollPanePropertyChange(PropertyChangeEvent e) {
1199             String propertyName = e.getPropertyName();
1200 
1201             if (propertyName == "verticalScrollBarDisplayPolicy") {
1202                 updateScrollBarDisplayPolicy(e);
1203             }
1204             else if (propertyName == "horizontalScrollBarDisplayPolicy") {
1205                 updateScrollBarDisplayPolicy(e);
1206             }
1207             else if (propertyName == "viewport") {
1208                 updateViewport(e);
1209             }
1210             else if (propertyName == "rowHeader") {
1211                 updateRowHeader(e);
1212             }
1213             else if (propertyName == "columnHeader") {
1214                 updateColumnHeader(e);
1215             }
1216             else if (propertyName == "verticalScrollBar") {
1217                 updateVerticalScrollBar(e);
1218             }
1219             else if (propertyName == "horizontalScrollBar") {
1220                 updateHorizontalScrollBar(e);
1221             }
1222             else if (propertyName == "componentOrientation") {
1223                 scrollpane.revalidate();
1224                 scrollpane.repaint();
1225             }
1226         }
1227 
1228         // PropertyChangeListener for the horizontal and vertical scrollbars.
1229         private void sbPropertyChange(PropertyChangeEvent e) {
1230             String propertyName = e.getPropertyName();
1231             Object source = e.getSource();
1232 
1233             if ("model" == propertyName) {
1234                 JScrollBar sb = scrollpane.getVerticalScrollBar();
1235                 BoundedRangeModel oldModel = (BoundedRangeModel)e.
1236                                      getOldValue();
1237                 ChangeListener cl = null;
1238 
1239                 if (source == sb) {
1240                     cl = vsbChangeListener;
1241                 }
1242                 else if (source == scrollpane.getHorizontalScrollBar()) {
1243                     sb = scrollpane.getHorizontalScrollBar();
1244                     cl = hsbChangeListener;
1245                 }
1246                 if (cl != null) {
1247                     if (oldModel != null) {
1248                         oldModel.removeChangeListener(cl);
1249                     }
1250                     if (sb.getModel() != null) {
1251                         sb.getModel().addChangeListener(cl);
1252                     }
1253                 }
1254             }
1255             else if ("componentOrientation" == propertyName) {
1256                 if (source == scrollpane.getHorizontalScrollBar()) {
1257                     JScrollBar hsb = scrollpane.getHorizontalScrollBar();
1258                     JViewport viewport = scrollpane.getViewport();
1259                     Point p = viewport.getViewPosition();
1260                     if (scrollpane.getComponentOrientation().isLeftToRight()) {
1261                         p.x = hsb.getValue();
1262                     } else {
1263                         p.x = viewport.getViewSize().width - viewport.getExtentSize().width - hsb.getValue();
1264                     }
1265                     viewport.setViewPosition(p);
1266                 }
1267             }
1268         }
1269     }
1270 }