1/*
2 * Copyright (c) 1997, 2016, 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
26package javax.swing.plaf.basic;
27
28import sun.swing.DefaultLookup;
29import sun.swing.UIAction;
30
31import javax.swing.*;
32import javax.swing.event.*;
33import javax.swing.border.*;
34import javax.swing.plaf.*;
35
36import java.beans.PropertyChangeListener;
37import java.beans.PropertyChangeEvent;
38
39import java.awt.Component;
40import java.awt.Rectangle;
41import java.awt.Dimension;
42import java.awt.Point;
43import java.awt.Insets;
44import java.awt.Graphics;
45import java.awt.event.*;
46
47/**
48 * A default L&F implementation of ScrollPaneUI.
49 *
50 * @author Hans Muller
51 */
52public 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                        || e.isShiftDown()) {
967                    toScroll = scrollpane.getHorizontalScrollBar();
968                    if (toScroll == null || !toScroll.isVisible()) {
969                        return;
970                    }
971                    orientation = SwingConstants.HORIZONTAL;
972                }
973
974                e.consume();
975
976                if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
977                    JViewport vp = scrollpane.getViewport();
978                    if (vp == null) { return; }
979                    Component comp = vp.getView();
980                    int units = Math.abs(e.getUnitsToScroll());
981
982                    // When the scrolling speed is set to maximum, it's possible
983                    // for a single wheel click to scroll by more units than
984                    // will fit in the visible area.  This makes it
985                    // hard/impossible to get to certain parts of the scrolling
986                    // Component with the wheel.  To make for more accurate
987                    // low-speed scrolling, we limit scrolling to the block
988                    // increment if the wheel was only rotated one click.
989                    boolean limitScroll = Math.abs(e.getWheelRotation()) == 1;
990
991                    // Check if we should use the visibleRect trick
992                    Object fastWheelScroll = toScroll.getClientProperty(
993                                               "JScrollBar.fastWheelScrolling");
994                    if (Boolean.TRUE == fastWheelScroll &&
995                        comp instanceof Scrollable) {
996                        // 5078454: Under maximum acceleration, we may scroll
997                        // by many 100s of units in ~1 second.
998                        //
999                        // BasicScrollBarUI.scrollByUnits() can bog down the EDT
1000                        // with repaints in this situation.  However, the
1001                        // Scrollable interface allows us to pass in an
1002                        // arbitrary visibleRect.  This allows us to accurately
1003                        // calculate the total scroll amount, and then update
1004                        // the GUI once.  This technique provides much faster
1005                        // accelerated wheel scrolling.
1006                        Scrollable scrollComp = (Scrollable) comp;
1007                        Rectangle viewRect = vp.getViewRect();
1008                        int startingX = viewRect.x;
1009                        boolean leftToRight =
1010                                 comp.getComponentOrientation().isLeftToRight();
1011                        int scrollMin = toScroll.getMinimum();
1012                        int scrollMax = toScroll.getMaximum() -
1013                                        toScroll.getModel().getExtent();
1014
1015                        if (limitScroll) {
1016                            int blockIncr =
1017                                scrollComp.getScrollableBlockIncrement(viewRect,
1018                                                                    orientation,
1019                                                                    direction);
1020                            if (direction < 0) {
1021                                scrollMin = Math.max(scrollMin,
1022                                               toScroll.getValue() - blockIncr);
1023                            }
1024                            else {
1025                                scrollMax = Math.min(scrollMax,
1026                                               toScroll.getValue() + blockIncr);
1027                            }
1028                        }
1029
1030                        for (int i = 0; i < units; i++) {
1031                            int unitIncr =
1032                                scrollComp.getScrollableUnitIncrement(viewRect,
1033                                                        orientation, direction);
1034                            // Modify the visible rect for the next unit, and
1035                            // check to see if we're at the end already.
1036                            if (orientation == SwingConstants.VERTICAL) {
1037                                if (direction < 0) {
1038                                    viewRect.y -= unitIncr;
1039                                    if (viewRect.y <= scrollMin) {
1040                                        viewRect.y = scrollMin;
1041                                        break;
1042                                    }
1043                                }
1044                                else { // (direction > 0
1045                                    viewRect.y += unitIncr;
1046                                    if (viewRect.y >= scrollMax) {
1047                                        viewRect.y = scrollMax;
1048                                        break;
1049                                    }
1050                                }
1051                            }
1052                            else {
1053                                // Scroll left
1054                                if ((leftToRight && direction < 0) ||
1055                                    (!leftToRight && direction > 0)) {
1056                                    viewRect.x -= unitIncr;
1057                                    if (leftToRight) {
1058                                        if (viewRect.x < scrollMin) {
1059                                            viewRect.x = scrollMin;
1060                                            break;
1061                                        }
1062                                    }
1063                                }
1064                                // Scroll right
1065                                else if ((leftToRight && direction > 0) ||
1066                                    (!leftToRight && direction < 0)) {
1067                                    viewRect.x += unitIncr;
1068                                    if (leftToRight) {
1069                                        if (viewRect.x > scrollMax) {
1070                                            viewRect.x = scrollMax;
1071                                            break;
1072                                        }
1073                                    }
1074                                }
1075                                else {
1076                                    assert false : "Non-sensical ComponentOrientation / scroll direction";
1077                                }
1078                            }
1079                        }
1080                        // Set the final view position on the ScrollBar
1081                        if (orientation == SwingConstants.VERTICAL) {
1082                            toScroll.setValue(viewRect.y);
1083                        }
1084                        else {
1085                            if (leftToRight) {
1086                                toScroll.setValue(viewRect.x);
1087                            }
1088                            else {
1089                                // rightToLeft scrollbars are oriented with
1090                                // minValue on the right and maxValue on the
1091                                // left.
1092                                int newPos = toScroll.getValue() -
1093                                                       (viewRect.x - startingX);
1094                                if (newPos < scrollMin) {
1095                                    newPos = scrollMin;
1096                                }
1097                                else if (newPos > scrollMax) {
1098                                    newPos = scrollMax;
1099                                }
1100                                toScroll.setValue(newPos);
1101                            }
1102                        }
1103                    }
1104                    else {
1105                        // Viewport's view is not a Scrollable, or fast wheel
1106                        // scrolling is not enabled.
1107                        BasicScrollBarUI.scrollByUnits(toScroll, direction,
1108                                                       units, limitScroll);
1109                    }
1110                }
1111                else if (e.getScrollType() ==
1112                         MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
1113                    BasicScrollBarUI.scrollByBlock(toScroll, direction);
1114                }
1115            }
1116        }
1117
1118        //
1119        // ChangeListener: This is added to the vieport, and hsb/vsb models.
1120        //
1121        public void stateChanged(ChangeEvent e) {
1122            JViewport viewport = scrollpane.getViewport();
1123
1124            if (viewport != null) {
1125                if (e.getSource() == viewport) {
1126                    syncScrollPaneWithViewport();
1127                }
1128                else {
1129                    JScrollBar hsb = scrollpane.getHorizontalScrollBar();
1130                    if (hsb != null && e.getSource() == hsb.getModel()) {
1131                        hsbStateChanged(viewport, e);
1132                    }
1133                    else {
1134                        JScrollBar vsb = scrollpane.getVerticalScrollBar();
1135                        if (vsb != null && e.getSource() == vsb.getModel()) {
1136                            vsbStateChanged(viewport, e);
1137                        }
1138                    }
1139                }
1140            }
1141        }
1142
1143        private void vsbStateChanged(JViewport viewport, ChangeEvent e) {
1144            BoundedRangeModel model = (BoundedRangeModel)(e.getSource());
1145            Point p = viewport.getViewPosition();
1146            p.y = model.getValue();
1147            viewport.setViewPosition(p);
1148        }
1149
1150        private void hsbStateChanged(JViewport viewport, ChangeEvent e) {
1151            BoundedRangeModel model = (BoundedRangeModel)(e.getSource());
1152            Point p = viewport.getViewPosition();
1153            int value = model.getValue();
1154            if (scrollpane.getComponentOrientation().isLeftToRight()) {
1155                p.x = value;
1156            } else {
1157                int max = viewport.getViewSize().width;
1158                int extent = viewport.getExtentSize().width;
1159                int oldX = p.x;
1160
1161                /* Set new X coordinate based on "value".
1162                 */
1163                p.x = max - extent - value;
1164
1165                /* If setValue() was called before "extent" was fixed,
1166                 * turn setValueCalled flag on.
1167                 */
1168                if ((extent == 0) && (value != 0) && (oldX == max)) {
1169                    setValueCalled = true;
1170                } else {
1171                    /* When a pane without a horizontal scroll bar was
1172                     * reduced and the bar appeared, the viewport should
1173                     * show the right side of the view.
1174                     */
1175                    if ((extent != 0) && (oldX < 0) && (p.x == 0)) {
1176                        p.x += value;
1177                    }
1178                }
1179            }
1180            viewport.setViewPosition(p);
1181        }
1182
1183        //
1184        // PropertyChangeListener: This is installed on both the JScrollPane
1185        // and the horizontal/vertical scrollbars.
1186        //
1187
1188        // Listens for changes in the model property and reinstalls the
1189        // horizontal/vertical PropertyChangeListeners.
1190        public void propertyChange(PropertyChangeEvent e) {
1191            if (e.getSource() == scrollpane) {
1192                scrollPanePropertyChange(e);
1193            }
1194            else {
1195                sbPropertyChange(e);
1196            }
1197        }
1198
1199        private void scrollPanePropertyChange(PropertyChangeEvent e) {
1200            String propertyName = e.getPropertyName();
1201
1202            if (propertyName == "verticalScrollBarDisplayPolicy") {
1203                updateScrollBarDisplayPolicy(e);
1204            }
1205            else if (propertyName == "horizontalScrollBarDisplayPolicy") {
1206                updateScrollBarDisplayPolicy(e);
1207            }
1208            else if (propertyName == "viewport") {
1209                updateViewport(e);
1210            }
1211            else if (propertyName == "rowHeader") {
1212                updateRowHeader(e);
1213            }
1214            else if (propertyName == "columnHeader") {
1215                updateColumnHeader(e);
1216            }
1217            else if (propertyName == "verticalScrollBar") {
1218                updateVerticalScrollBar(e);
1219            }
1220            else if (propertyName == "horizontalScrollBar") {
1221                updateHorizontalScrollBar(e);
1222            }
1223            else if (propertyName == "componentOrientation") {
1224                scrollpane.revalidate();
1225                scrollpane.repaint();
1226            }
1227        }
1228
1229        // PropertyChangeListener for the horizontal and vertical scrollbars.
1230        private void sbPropertyChange(PropertyChangeEvent e) {
1231            String propertyName = e.getPropertyName();
1232            Object source = e.getSource();
1233
1234            if ("model" == propertyName) {
1235                JScrollBar sb = scrollpane.getVerticalScrollBar();
1236                BoundedRangeModel oldModel = (BoundedRangeModel)e.
1237                                     getOldValue();
1238                ChangeListener cl = null;
1239
1240                if (source == sb) {
1241                    cl = vsbChangeListener;
1242                }
1243                else if (source == scrollpane.getHorizontalScrollBar()) {
1244                    sb = scrollpane.getHorizontalScrollBar();
1245                    cl = hsbChangeListener;
1246                }
1247                if (cl != null) {
1248                    if (oldModel != null) {
1249                        oldModel.removeChangeListener(cl);
1250                    }
1251                    if (sb.getModel() != null) {
1252                        sb.getModel().addChangeListener(cl);
1253                    }
1254                }
1255            }
1256            else if ("componentOrientation" == propertyName) {
1257                if (source == scrollpane.getHorizontalScrollBar()) {
1258                    JScrollBar hsb = scrollpane.getHorizontalScrollBar();
1259                    JViewport viewport = scrollpane.getViewport();
1260                    Point p = viewport.getViewPosition();
1261                    if (scrollpane.getComponentOrientation().isLeftToRight()) {
1262                        p.x = hsb.getValue();
1263                    } else {
1264                        p.x = viewport.getViewSize().width - viewport.getExtentSize().width - hsb.getValue();
1265                    }
1266                    viewport.setViewPosition(p);
1267                }
1268            }
1269        }
1270    }
1271}
1272