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