1 /* BasicScrollPaneUI.java
2 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package javax.swing.plaf.basic;
41 import java.awt.Dimension;
42 import java.awt.Graphics;
43 import java.awt.Point;
44 import java.awt.Rectangle;
45 import java.awt.event.ActionEvent;
46 import java.awt.event.ContainerEvent;
47 import java.awt.event.ContainerListener;
48 import java.awt.event.MouseWheelEvent;
49 import java.awt.event.MouseWheelListener;
50 import java.beans.PropertyChangeEvent;
51 import java.beans.PropertyChangeListener;
53 import javax.swing.AbstractAction;
54 import javax.swing.ActionMap;
55 import javax.swing.InputMap;
56 import javax.swing.JComponent;
57 import javax.swing.JScrollBar;
58 import javax.swing.JScrollPane;
59 import javax.swing.JSlider;
60 import javax.swing.JViewport;
61 import javax.swing.LookAndFeel;
62 import javax.swing.ScrollPaneConstants;
63 import javax.swing.ScrollPaneLayout;
64 import javax.swing.SwingUtilities;
65 import javax.swing.UIManager;
66 import javax.swing.border.Border;
67 import javax.swing.event.ChangeEvent;
68 import javax.swing.event.ChangeListener;
69 import javax.swing.plaf.ActionMapUIResource;
70 import javax.swing.plaf.ComponentUI;
71 import javax.swing.plaf.ScrollPaneUI;
72 import javax.swing.plaf.UIResource;
75 * A UI delegate for the {@link JScrollPane} component.
77 public class BasicScrollPaneUI extends ScrollPaneUI
78 implements ScrollPaneConstants
82 * Listens for changes in the state of the horizontal scrollbar's model and
83 * updates the scrollpane accordingly.
85 * @author Roman Kennke (kennke@aicas.com)
87 public class HSBChangeListener implements ChangeListener
91 * Receives notification when the state of the horizontal scrollbar
94 * @param event the change event
96 public void stateChanged(ChangeEvent event)
98 JScrollBar hsb = scrollpane.getHorizontalScrollBar();
99 JViewport vp = scrollpane.getViewport();
100 Point viewPosition = vp.getViewPosition();
101 viewPosition.x = hsb.getValue();
102 vp.setViewPosition(viewPosition);
108 * Listens for changes in the state of the vertical scrollbar's model and
109 * updates the scrollpane accordingly.
111 * @author Roman Kennke (kennke@aicas.com)
113 public class VSBChangeListener implements ChangeListener
117 * Receives notification when the state of the vertical scrollbar
120 * @param event the change event
122 public void stateChanged(ChangeEvent event)
124 JScrollBar vsb = scrollpane.getVerticalScrollBar();
125 JViewport vp = scrollpane.getViewport();
126 Point viewPosition = vp.getViewPosition();
127 viewPosition.y = vsb.getValue();
128 vp.setViewPosition(viewPosition);
134 * Listens for changes of the viewport's extent size and updates the
135 * scrollpane accordingly.
137 * @author Roman Kennke (kennke@aicas.com)
139 public class ViewportChangeHandler implements ChangeListener
143 * Receives notification when the view's size, position or extent size
144 * changes. When the extents size has changed, this method calls
145 * {@link BasicScrollPaneUI#syncScrollPaneWithViewport()} to adjust the
146 * scrollbars extents as well.
148 * @param event the change event
150 public void stateChanged(ChangeEvent event)
152 syncScrollPaneWithViewport();
158 * Listens for property changes on the scrollpane and update the view
161 * @author Roman Kennke (kennke@aicas.com)
163 public class PropertyChangeHandler implements PropertyChangeListener
167 * Receives notification when any of the scrollpane's bound property
168 * changes. This method calls the appropriate update method on the
169 * <code>ScrollBarUI</code>.
171 * @param e the property change event
173 * @see BasicScrollPaneUI#updateColumnHeader(PropertyChangeEvent)
174 * @see BasicScrollPaneUI#updateRowHeader(PropertyChangeEvent)
175 * @see BasicScrollPaneUI#updateScrollBarDisplayPolicy(PropertyChangeEvent)
176 * @see BasicScrollPaneUI#updateViewport(PropertyChangeEvent)
178 public void propertyChange(PropertyChangeEvent e)
180 String propName = e.getPropertyName();
181 if (propName.equals("viewport"))
183 else if (propName.equals("rowHeader"))
185 else if (propName.equals("columnHeader"))
186 updateColumnHeader(e);
187 else if (propName.equals("horizontalScrollBarPolicy")
188 || e.getPropertyName().equals("verticalScrollBarPolicy"))
189 updateScrollBarDisplayPolicy(e);
190 else if (propName.equals("verticalScrollBar"))
192 JScrollBar oldSb = (JScrollBar) e.getOldValue();
193 oldSb.getModel().removeChangeListener(vsbChangeListener);
194 JScrollBar newSb = (JScrollBar) e.getNewValue();
195 newSb.getModel().addChangeListener(vsbChangeListener);
197 else if (propName.equals("horizontalScrollBar"))
199 JScrollBar oldSb = (JScrollBar) e.getOldValue();
200 oldSb.getModel().removeChangeListener(hsbChangeListener);
201 JScrollBar newSb = (JScrollBar) e.getNewValue();
202 newSb.getModel().addChangeListener(hsbChangeListener);
209 * Listens for mouse wheel events and update the scrollpane accordingly.
211 * @author Roman Kennke (kennke@aicas.com)
215 protected class MouseWheelHandler implements MouseWheelListener
218 * Use to compute the visible rectangle.
220 final Rectangle rect = new Rectangle();
223 * Scroll with the mouse wheel.
225 * @author Audrius Meskauskas (audriusa@Bioinformatics.org)
227 public void mouseWheelMoved(MouseWheelEvent e)
229 if (scrollpane.isWheelScrollingEnabled() && e.getScrollAmount() != 0)
231 // Try to scroll vertically first.
232 JScrollBar scrollBar = scrollpane.getVerticalScrollBar();
233 if (scrollBar == null || ! scrollBar.isVisible())
234 scrollBar = scrollpane.getHorizontalScrollBar();
235 if (scrollBar != null && scrollBar.isVisible())
237 int direction = e.getWheelRotation() < 0 ? -1 : 1;
238 int scrollType = e.getScrollType();
239 if (scrollType == MouseWheelEvent.WHEEL_UNIT_SCROLL)
240 BasicScrollBarUI.scrollByUnits(scrollBar, direction,
241 e.getScrollAmount());
242 else if (scrollType == MouseWheelEvent.WHEEL_BLOCK_SCROLL)
243 BasicScrollBarUI.scrollByBlock(scrollBar, direction);
250 * Adds/removes the mouse wheel listener when the component is added/removed
251 * to/from the scroll pane view port.
253 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
255 class ViewportContainerListener implements ContainerListener
258 * Add the mouse wheel listener, allowing to scroll with the mouse.
260 public void componentAdded(ContainerEvent e)
262 e.getChild().addMouseWheelListener(mouseWheelListener);
266 * Remove the mouse wheel listener.
268 public void componentRemoved(ContainerEvent e)
270 e.getChild().removeMouseWheelListener(mouseWheelListener);
275 * The number of pixels by that we should scroll the content that does
276 * not implement Scrollable.
278 static int SCROLL_NON_SCROLLABLES = 10;
281 * The number of rows to scroll per mouse wheel click. From impression,
282 * Sun seems using the value 3.
284 static int ROWS_PER_WHEEL_CLICK = 3;
286 /** The Scrollpane for which the UI is provided by this class. */
287 protected JScrollPane scrollpane;
290 * The horizontal scrollbar listener.
292 protected ChangeListener hsbChangeListener;
295 * The vertical scrollbar listener.
297 protected ChangeListener vsbChangeListener;
300 * The viewport listener.
302 protected ChangeListener viewportChangeListener;
305 * The scrollpane property change listener.
307 protected PropertyChangeListener spPropertyChangeListener;
310 * The mousewheel listener for the scrollpane.
312 MouseWheelListener mouseWheelListener;
315 * The listener to add and remove the mouse wheel listener to/from
316 * the component container.
318 ContainerListener containerListener;
320 public static ComponentUI createUI(final JComponent c)
322 return new BasicScrollPaneUI();
325 protected void installDefaults(JScrollPane p)
328 LookAndFeel.installColorsAndFont(p, "ScrollPane.background",
329 "ScrollPane.foreground",
331 LookAndFeel.installBorder(p, "ScrollPane.border");
333 // Install Viewport border.
334 Border vpBorder = p.getViewportBorder();
335 if (vpBorder == null || vpBorder instanceof UIResource)
337 vpBorder = UIManager.getBorder("ScrollPane.viewportBorder");
338 p.setViewportBorder(vpBorder);
344 protected void uninstallDefaults(JScrollPane p)
346 LookAndFeel.uninstallBorder(p);
347 Border vpBorder = p.getViewportBorder();
348 if (vpBorder != null && vpBorder instanceof UIResource)
349 p.setViewportBorder(null);
352 public void installUI(final JComponent c)
355 installDefaults((JScrollPane) c);
356 installListeners((JScrollPane) c);
357 installKeyboardActions((JScrollPane) c);
361 * Installs the listeners on the scrollbars, the viewport and the scrollpane.
363 * @param sp the scrollpane on which to install the listeners
365 protected void installListeners(JScrollPane sp)
367 if (spPropertyChangeListener == null)
368 spPropertyChangeListener = createPropertyChangeListener();
369 sp.addPropertyChangeListener(spPropertyChangeListener);
371 if (hsbChangeListener == null)
372 hsbChangeListener = createHSBChangeListener();
373 sp.getHorizontalScrollBar().getModel().addChangeListener(hsbChangeListener);
375 if (vsbChangeListener == null)
376 vsbChangeListener = createVSBChangeListener();
377 sp.getVerticalScrollBar().getModel().addChangeListener(vsbChangeListener);
379 if (viewportChangeListener == null)
380 viewportChangeListener = createViewportChangeListener();
382 if (mouseWheelListener == null)
383 mouseWheelListener = createMouseWheelListener();
385 if (containerListener == null)
386 containerListener = new ViewportContainerListener();
388 JViewport v = sp.getViewport();
389 v.addChangeListener(viewportChangeListener);
390 v.addContainerListener(containerListener);
392 // Add mouse wheel listeners to the componets that are probably already
394 for (int i = 0; i < v.getComponentCount(); i++)
395 v.getComponent(i).addMouseWheelListener(mouseWheelListener);
398 InputMap getInputMap(int condition)
400 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
401 return (InputMap) UIManager.get("ScrollPane.ancestorInputMap");
406 * Returns the action map for the {@link JScrollPane}. All scroll panes
407 * share a single action map which is created the first time this method is
408 * called, then stored in the UIDefaults table for subsequent access.
410 * @return The shared action map.
412 ActionMap getActionMap()
414 ActionMap map = (ActionMap) UIManager.get("ScrollPane.actionMap");
416 if (map == null) // first time here
418 map = createActionMap();
420 UIManager.put("ScrollPane.actionMap", map);
426 * Creates the action map shared by all {@link JSlider} instances.
427 * This method is called once by {@link #getActionMap()} when it
428 * finds no action map in the UIDefaults table...after the map is
429 * created, it gets added to the defaults table so that subsequent
430 * calls to {@link #getActionMap()} will return the same shared
433 * @return The action map.
435 ActionMap createActionMap()
437 ActionMap map = new ActionMapUIResource();
438 map.put("scrollLeft",
439 new AbstractAction("scrollLeft") {
440 public void actionPerformed(ActionEvent event)
442 JScrollPane sp = (JScrollPane) event.getSource();
443 JScrollBar sb = sp.getHorizontalScrollBar();
446 int delta = sb.getBlockIncrement(-1);
447 sb.setValue(sb.getValue() + delta);
453 new AbstractAction("scrollEnd") {
454 public void actionPerformed(ActionEvent event)
456 JScrollPane sp = (JScrollPane) event.getSource();
457 JScrollBar sb1 = sp.getHorizontalScrollBar();
460 sb1.setValue(sb1.getMaximum());
462 JScrollBar sb2 = sp.getVerticalScrollBar();
465 sb2.setValue(sb2.getMaximum());
470 map.put("unitScrollUp",
471 new AbstractAction("unitScrollUp") {
472 public void actionPerformed(ActionEvent event)
474 JScrollPane sp = (JScrollPane) event.getSource();
475 JScrollBar sb = sp.getVerticalScrollBar();
478 int delta = sb.getUnitIncrement(-1);
479 sb.setValue(sb.getValue() + delta);
484 map.put("unitScrollLeft",
485 new AbstractAction("unitScrollLeft") {
486 public void actionPerformed(ActionEvent event)
488 JScrollPane sp = (JScrollPane) event.getSource();
489 JScrollBar sb = sp.getHorizontalScrollBar();
492 int delta = sb.getUnitIncrement(-1);
493 sb.setValue(sb.getValue() + delta);
499 new AbstractAction("scrollUp") {
500 public void actionPerformed(ActionEvent event)
502 JScrollPane sp = (JScrollPane) event.getSource();
503 JScrollBar sb = sp.getVerticalScrollBar();
506 int delta = sb.getBlockIncrement(-1);
507 sb.setValue(sb.getValue() + delta);
512 map.put("scrollRight",
513 new AbstractAction("scrollRight") {
514 public void actionPerformed(ActionEvent event)
516 JScrollPane sp = (JScrollPane) event.getSource();
517 JScrollBar sb = sp.getHorizontalScrollBar();
520 int delta = sb.getBlockIncrement(1);
521 sb.setValue(sb.getValue() + delta);
526 map.put("scrollHome",
527 new AbstractAction("scrollHome") {
528 public void actionPerformed(ActionEvent event)
530 JScrollPane sp = (JScrollPane) event.getSource();
531 JScrollBar sb1 = sp.getHorizontalScrollBar();
534 sb1.setValue(sb1.getMinimum());
536 JScrollBar sb2 = sp.getVerticalScrollBar();
539 sb2.setValue(sb2.getMinimum());
544 map.put("scrollDown",
545 new AbstractAction("scrollDown") {
546 public void actionPerformed(ActionEvent event)
548 JScrollPane sp = (JScrollPane) event.getSource();
549 JScrollBar sb = sp.getVerticalScrollBar();
552 int delta = sb.getBlockIncrement(1);
553 sb.setValue(sb.getValue() + delta);
558 map.put("unitScrollDown",
559 new AbstractAction("unitScrollDown") {
560 public void actionPerformed(ActionEvent event)
562 JScrollPane sp = (JScrollPane) event.getSource();
563 JScrollBar sb = sp.getVerticalScrollBar();
566 int delta = sb.getUnitIncrement(1);
567 sb.setValue(sb.getValue() + delta);
572 map.put("unitScrollRight",
573 new AbstractAction("unitScrollRight") {
574 public void actionPerformed(ActionEvent event)
576 JScrollPane sp = (JScrollPane) event.getSource();
577 JScrollBar sb = sp.getHorizontalScrollBar();
580 int delta = sb.getUnitIncrement(1);
581 sb.setValue(sb.getValue() + delta);
590 * Installs additional keyboard actions on the scrollpane. This is a hook
591 * method provided to subclasses in order to install their own keyboard
594 * @param sp the scrollpane to install keyboard actions on
596 protected void installKeyboardActions(JScrollPane sp)
598 InputMap keyMap = getInputMap(
599 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
600 SwingUtilities.replaceUIInputMap(sp,
601 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keyMap);
602 ActionMap map = getActionMap();
603 SwingUtilities.replaceUIActionMap(sp, map);
607 * Uninstalls all keyboard actions from the JScrollPane that have been
608 * installed by {@link #installKeyboardActions}. This is a hook method
609 * provided to subclasses to add their own keyboard actions.
611 * @param sp the scrollpane to uninstall keyboard actions from
613 protected void uninstallKeyboardActions(JScrollPane sp)
615 SwingUtilities.replaceUIActionMap(sp, null);
616 SwingUtilities.replaceUIInputMap(sp,
617 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
621 * Creates and returns the change listener for the horizontal scrollbar.
623 * @return the change listener for the horizontal scrollbar
625 protected ChangeListener createHSBChangeListener()
627 return new HSBChangeListener();
631 * Creates and returns the change listener for the vertical scrollbar.
633 * @return the change listener for the vertical scrollbar
635 protected ChangeListener createVSBChangeListener()
637 return new VSBChangeListener();
641 * Creates and returns the change listener for the viewport.
643 * @return the change listener for the viewport
645 protected ChangeListener createViewportChangeListener()
647 return new ViewportChangeHandler();
651 * Creates and returns the property change listener for the scrollpane.
653 * @return the property change listener for the scrollpane
655 protected PropertyChangeListener createPropertyChangeListener()
657 return new PropertyChangeHandler();
661 * Creates and returns the mouse wheel listener for the scrollpane.
663 * @return the mouse wheel listener for the scrollpane
667 protected MouseWheelListener createMouseWheelListener()
669 return new MouseWheelHandler();
672 public void uninstallUI(final JComponent c)
674 uninstallDefaults((JScrollPane) c);
675 uninstallListeners(c);
676 installKeyboardActions((JScrollPane) c);
680 * Uninstalls all the listeners that have been installed in
681 * {@link #installListeners(JScrollPane)}.
683 * @param c the scrollpane from which to uninstall the listeners
685 protected void uninstallListeners(JComponent c)
687 JScrollPane sp = (JScrollPane) c;
688 sp.removePropertyChangeListener(spPropertyChangeListener);
689 sp.getHorizontalScrollBar().getModel()
690 .removeChangeListener(hsbChangeListener);
691 sp.getVerticalScrollBar().getModel()
692 .removeChangeListener(vsbChangeListener);
694 JViewport v = sp.getViewport();
695 v.removeChangeListener(viewportChangeListener);
696 v.removeContainerListener(containerListener);
698 for (int i = 0; i < v.getComponentCount(); i++)
699 v.getComponent(i).removeMouseWheelListener(mouseWheelListener);
703 public Dimension getMinimumSize(JComponent c)
705 JScrollPane p = (JScrollPane) c;
706 ScrollPaneLayout sl = (ScrollPaneLayout) p.getLayout();
707 return sl.minimumLayoutSize(c);
710 public void paint(Graphics g, JComponent c)
712 Border vpBorder = scrollpane.getViewportBorder();
713 if (vpBorder != null)
715 Rectangle r = scrollpane.getViewportBorderBounds();
716 vpBorder.paintBorder(scrollpane, g, r.x, r.y, r.width, r.height);
721 * Synchronizes the scrollbar and header settings positions and extent
722 * with the viewport's view position and extent.
724 protected void syncScrollPaneWithViewport()
726 JViewport vp = scrollpane.getViewport();
730 Dimension extentSize = vp.getExtentSize();
731 Point viewPos = vp.getViewPosition();
732 Dimension viewSize = vp.getViewSize();
734 // Update the vertical scrollbar.
735 JScrollBar vsb = scrollpane.getVerticalScrollBar();
738 int extent = extentSize.height;
739 int max = viewSize.height;
740 int val = Math.max(0, Math.min(viewPos.y, max - extent));
741 vsb.setValues(val, extent, 0, max);
744 // Update the horizontal scrollbar.
745 JScrollBar hsb = scrollpane.getHorizontalScrollBar();
748 int extent = extentSize.width;
749 int max = viewSize.width;
750 int val = Math.max(0, Math.min(viewPos.x, max - extent));
751 hsb.setValues(val, extent, 0, max);
754 // Update the row header.
755 JViewport rowHeader = scrollpane.getRowHeader();
756 if (rowHeader != null)
758 Point p = new Point(0, viewPos.y);
759 rowHeader.setViewPosition(p);
762 // Update the column header.
763 JViewport colHeader = scrollpane.getColumnHeader();
764 if (colHeader != null)
766 Point p = new Point(viewPos.x, 0);
767 colHeader.setViewPosition(p);
773 * Receives notification when the <code>columnHeader</code> property has
774 * changed on the scrollpane.
776 * @param ev the property change event
778 protected void updateColumnHeader(PropertyChangeEvent ev)
780 // TODO: Find out what should be done here. Or is this only a hook?
784 * Receives notification when the <code>rowHeader</code> property has changed
787 * @param ev the property change event
789 protected void updateRowHeader(PropertyChangeEvent ev)
791 // TODO: Find out what should be done here. Or is this only a hook?
795 * Receives notification when the <code>scrollBarDisplayPolicy</code>
796 * property has changed on the scrollpane.
798 * @param ev the property change event
800 protected void updateScrollBarDisplayPolicy(PropertyChangeEvent ev)
802 scrollpane.revalidate();
803 scrollpane.repaint();
807 * Receives notification when the <code>viewport</code> property has changed
810 * This method sets removes the viewportChangeListener from the old viewport
811 * and adds it to the new viewport.
813 * @param ev the property change event
815 protected void updateViewport(PropertyChangeEvent ev)
817 JViewport oldViewport = (JViewport) ev.getOldValue();
818 oldViewport.removeChangeListener(viewportChangeListener);
819 JViewport newViewport = (JViewport) ev.getNewValue();
820 newViewport.addChangeListener(viewportChangeListener);
821 syncScrollPaneWithViewport();