001 /* BasicScrollBarUI.java --
002 Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.plaf.basic;
040
041 import java.awt.Color;
042 import java.awt.Component;
043 import java.awt.Container;
044 import java.awt.Dimension;
045 import java.awt.Graphics;
046 import java.awt.Insets;
047 import java.awt.LayoutManager;
048 import java.awt.Rectangle;
049 import java.awt.event.ActionEvent;
050 import java.awt.event.ActionListener;
051 import java.awt.event.MouseAdapter;
052 import java.awt.event.MouseEvent;
053 import java.awt.event.MouseMotionListener;
054 import java.beans.PropertyChangeEvent;
055 import java.beans.PropertyChangeListener;
056
057 import javax.swing.AbstractAction;
058 import javax.swing.ActionMap;
059 import javax.swing.BoundedRangeModel;
060 import javax.swing.InputMap;
061 import javax.swing.JButton;
062 import javax.swing.JComponent;
063 import javax.swing.JScrollBar;
064 import javax.swing.JSlider;
065 import javax.swing.LookAndFeel;
066 import javax.swing.SwingConstants;
067 import javax.swing.SwingUtilities;
068 import javax.swing.Timer;
069 import javax.swing.UIManager;
070 import javax.swing.event.ChangeEvent;
071 import javax.swing.event.ChangeListener;
072 import javax.swing.plaf.ActionMapUIResource;
073 import javax.swing.plaf.ComponentUI;
074 import javax.swing.plaf.ScrollBarUI;
075
076 /**
077 * The Basic Look and Feel UI delegate for JScrollBar.
078 */
079 public class BasicScrollBarUI extends ScrollBarUI implements LayoutManager,
080 SwingConstants
081 {
082 /**
083 * A helper class that listens to the two JButtons on each end of the
084 * JScrollBar.
085 */
086 protected class ArrowButtonListener extends MouseAdapter
087 {
088
089 /**
090 * Move the thumb in the direction specified by the button's arrow. If
091 * this button is held down, then it should keep moving the thumb.
092 *
093 * @param e The MouseEvent fired by the JButton.
094 */
095 public void mousePressed(MouseEvent e)
096 {
097 scrollTimer.stop();
098 scrollListener.setScrollByBlock(false);
099 if (e.getSource() == incrButton)
100 scrollListener.setDirection(POSITIVE_SCROLL);
101 else if (e.getSource() == decrButton)
102 scrollListener.setDirection(NEGATIVE_SCROLL);
103 scrollTimer.setDelay(100);
104 scrollTimer.start();
105 }
106
107 /**
108 * Stops the thumb when the JButton is released.
109 *
110 * @param e The MouseEvent fired by the JButton.
111 */
112 public void mouseReleased(MouseEvent e)
113 {
114 scrollTimer.stop();
115 scrollTimer.setDelay(300);
116 if (e.getSource() == incrButton)
117 scrollByUnit(POSITIVE_SCROLL);
118 else if (e.getSource() == decrButton)
119 scrollByUnit(NEGATIVE_SCROLL);
120 }
121 }
122
123 /**
124 * A helper class that listens to the ScrollBar's model for ChangeEvents.
125 */
126 protected class ModelListener implements ChangeListener
127 {
128 /**
129 * Called when the model changes.
130 *
131 * @param e The ChangeEvent fired by the model.
132 */
133 public void stateChanged(ChangeEvent e)
134 {
135 calculatePreferredSize();
136 updateThumbRect();
137 scrollbar.repaint();
138 }
139 }
140
141 /**
142 * A helper class that listens to the ScrollBar's properties.
143 */
144 public class PropertyChangeHandler implements PropertyChangeListener
145 {
146 /**
147 * Called when one of the ScrollBar's properties change.
148 *
149 * @param e The PropertyChangeEvent fired by the ScrollBar.
150 */
151 public void propertyChange(PropertyChangeEvent e)
152 {
153 if (e.getPropertyName().equals("model"))
154 {
155 ((BoundedRangeModel) e.getOldValue()).removeChangeListener(modelListener);
156 scrollbar.getModel().addChangeListener(modelListener);
157 updateThumbRect();
158 }
159 else if (e.getPropertyName().equals("orientation"))
160 {
161 uninstallListeners();
162 uninstallComponents();
163 uninstallDefaults();
164 installDefaults();
165 installComponents();
166 installListeners();
167 }
168 else if (e.getPropertyName().equals("enabled"))
169 {
170 Boolean b = (Boolean) e.getNewValue();
171 if (incrButton != null)
172 incrButton.setEnabled(b.booleanValue());
173 if (decrButton != null)
174 decrButton.setEnabled(b.booleanValue());
175 }
176 }
177 }
178
179 /**
180 * A helper class that listens for events from the timer that is used to
181 * move the thumb.
182 */
183 protected class ScrollListener implements ActionListener
184 {
185 /** The direction the thumb moves in. */
186 private transient int direction;
187
188 /** Whether movement will be in blocks. */
189 private transient boolean block;
190
191 /**
192 * Creates a new ScrollListener object. The default is scrolling
193 * positively with block movement.
194 */
195 public ScrollListener()
196 {
197 direction = POSITIVE_SCROLL;
198 block = true;
199 }
200
201 /**
202 * Creates a new ScrollListener object using the given direction and
203 * block.
204 *
205 * @param dir The direction to move in.
206 * @param block Whether movement will be in blocks.
207 */
208 public ScrollListener(int dir, boolean block)
209 {
210 direction = dir;
211 this.block = block;
212 }
213
214 /**
215 * Sets the direction to scroll in.
216 *
217 * @param direction The direction to scroll in.
218 */
219 public void setDirection(int direction)
220 {
221 this.direction = direction;
222 }
223
224 /**
225 * Sets whether scrolling will be done in blocks.
226 *
227 * @param block Whether scrolling will be in blocks.
228 */
229 public void setScrollByBlock(boolean block)
230 {
231 this.block = block;
232 }
233
234 /**
235 * Called every time the timer reaches its interval.
236 *
237 * @param e The ActionEvent fired by the timer.
238 */
239 public void actionPerformed(ActionEvent e)
240 {
241 if (block)
242 {
243 // Only need to check it if it's block scrolling
244 // We only block scroll if the click occurs
245 // in the track.
246 if (!trackListener.shouldScroll(direction))
247 {
248 trackHighlight = NO_HIGHLIGHT;
249 scrollbar.repaint();
250 return;
251 }
252 scrollByBlock(direction);
253 }
254 else
255 scrollByUnit(direction);
256 }
257 }
258
259 /**
260 * Helper class that listens for movement on the track.
261 */
262 protected class TrackListener extends MouseAdapter
263 implements MouseMotionListener
264 {
265 /** The current X coordinate of the mouse. */
266 protected int currentMouseX;
267
268 /** The current Y coordinate of the mouse. */
269 protected int currentMouseY;
270
271 /**
272 * The offset between the current mouse cursor and the current value of
273 * the scrollbar.
274 */
275 protected int offset;
276
277 /**
278 * This method is called when the mouse is being dragged.
279 *
280 * @param e The MouseEvent given.
281 */
282 public void mouseDragged(MouseEvent e)
283 {
284 currentMouseX = e.getX();
285 currentMouseY = e.getY();
286 if (scrollbar.getValueIsAdjusting())
287 {
288 int value;
289 if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL)
290 value = valueForXPosition(currentMouseX) - offset;
291 else
292 value = valueForYPosition(currentMouseY) - offset;
293
294 scrollbar.setValue(value);
295 }
296 }
297
298 /**
299 * This method is called when the mouse is moved.
300 *
301 * @param e The MouseEvent given.
302 */
303 public void mouseMoved(MouseEvent e)
304 {
305 if (thumbRect.contains(e.getPoint()))
306 thumbRollover = true;
307 else
308 thumbRollover = false;
309 }
310
311 /**
312 * This method is called when the mouse is pressed. When it is pressed,
313 * the thumb should move in blocks towards the cursor.
314 *
315 * @param e The MouseEvent given.
316 */
317 public void mousePressed(MouseEvent e)
318 {
319 currentMouseX = e.getX();
320 currentMouseY = e.getY();
321
322 int value;
323 if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL)
324 value = valueForXPosition(currentMouseX);
325 else
326 value = valueForYPosition(currentMouseY);
327
328 if (! thumbRect.contains(e.getPoint()))
329 {
330 scrollTimer.stop();
331 scrollListener.setScrollByBlock(true);
332 if (value > scrollbar.getValue())
333 {
334 trackHighlight = INCREASE_HIGHLIGHT;
335 scrollListener.setDirection(POSITIVE_SCROLL);
336 }
337 else
338 {
339 trackHighlight = DECREASE_HIGHLIGHT;
340 scrollListener.setDirection(NEGATIVE_SCROLL);
341 }
342 scrollTimer.setDelay(100);
343 scrollTimer.start();
344 }
345 else
346 {
347 // We'd like to keep track of where the cursor
348 // is inside the thumb.
349 // This works because the scrollbar's value represents
350 // "lower" edge of the thumb. The value at which
351 // the cursor is at must be greater or equal
352 // to that value.
353
354 scrollListener.setScrollByBlock(false);
355 scrollbar.setValueIsAdjusting(true);
356 offset = value - scrollbar.getValue();
357 }
358 scrollbar.repaint();
359 }
360
361 /**
362 * This method is called when the mouse is released. It should stop
363 * movement on the thumb
364 *
365 * @param e The MouseEvent given.
366 */
367 public void mouseReleased(MouseEvent e)
368 {
369 scrollTimer.stop();
370 scrollTimer.setDelay(300);
371 currentMouseX = e.getX();
372 currentMouseY = e.getY();
373
374 if (shouldScroll(POSITIVE_SCROLL))
375 scrollByBlock(POSITIVE_SCROLL);
376 else if (shouldScroll(NEGATIVE_SCROLL))
377 scrollByBlock(NEGATIVE_SCROLL);
378
379 trackHighlight = NO_HIGHLIGHT;
380 scrollListener.setScrollByBlock(false);
381 scrollbar.setValueIsAdjusting(true);
382 scrollbar.repaint();
383 }
384
385 /**
386 * A helper method that decides whether we should keep scrolling in the
387 * given direction.
388 *
389 * @param direction The direction to check for.
390 *
391 * @return Whether the thumb should keep scrolling.
392 */
393 boolean shouldScroll(int direction)
394 {
395 int value;
396 if (scrollbar.getOrientation() == HORIZONTAL)
397 value = valueForXPosition(currentMouseX);
398 else
399 value = valueForYPosition(currentMouseY);
400
401 if (thumbRect.contains(currentMouseX, currentMouseY))
402 return false;
403
404 if (direction == POSITIVE_SCROLL)
405 return value > scrollbar.getValue();
406 else
407 return value < scrollbar.getValue();
408 }
409 }
410
411 /** The listener that listens to the JButtons. */
412 protected ArrowButtonListener buttonListener;
413
414 /** The listener that listens to the model. */
415 protected ModelListener modelListener;
416
417 /** The listener that listens to the scrollbar for property changes. */
418 protected PropertyChangeListener propertyChangeListener;
419
420 /** The listener that listens to the timer. */
421 protected ScrollListener scrollListener;
422
423 /** The listener that listens for MouseEvents on the track. */
424 protected TrackListener trackListener;
425
426 /** The JButton that decrements the scrollbar's value. */
427 protected JButton decrButton;
428
429 /** The JButton that increments the scrollbar's value. */
430 protected JButton incrButton;
431
432 /** The dimensions of the maximum thumb size. */
433 protected Dimension maximumThumbSize;
434
435 /** The dimensions of the minimum thumb size. */
436 protected Dimension minimumThumbSize;
437
438 /** The color of the thumb. */
439 protected Color thumbColor;
440
441 /** The outer shadow of the thumb. */
442 protected Color thumbDarkShadowColor;
443
444 /** The top and left edge color for the thumb. */
445 protected Color thumbHighlightColor;
446
447 /** The outer light shadow for the thumb. */
448 protected Color thumbLightShadowColor;
449
450 /** The color that is used when the mouse press occurs in the track. */
451 protected Color trackHighlightColor;
452
453 /** The color of the track. */
454 protected Color trackColor;
455
456 /** The size and position of the track. */
457 protected Rectangle trackRect;
458
459 /** The size and position of the thumb. */
460 protected Rectangle thumbRect;
461
462 /** Indicates that the decrease highlight should be painted. */
463 protected static final int DECREASE_HIGHLIGHT = 1;
464
465 /** Indicates that the increase highlight should be painted. */
466 protected static final int INCREASE_HIGHLIGHT = 2;
467
468 /** Indicates that no highlight should be painted. */
469 protected static final int NO_HIGHLIGHT = 0;
470
471 /** Indicates that the scrolling direction is positive. */
472 private static final int POSITIVE_SCROLL = 1;
473
474 /** Indicates that the scrolling direction is negative. */
475 private static final int NEGATIVE_SCROLL = -1;
476
477 /** The cached preferred size for the scrollbar. */
478 private transient Dimension preferredSize;
479
480 /** The current highlight status. */
481 protected int trackHighlight;
482
483 /** FIXME: Use this for something (presumably mouseDragged) */
484 protected boolean isDragging;
485
486 /** The timer used to move the thumb when the mouse is held. */
487 protected Timer scrollTimer;
488
489 /** The scrollbar this UI is acting for. */
490 protected JScrollBar scrollbar;
491
492 /** True if the mouse is over the thumb. */
493 boolean thumbRollover;
494
495 /**
496 * This method adds a component to the layout.
497 *
498 * @param name The name to associate with the component that is added.
499 * @param child The Component to add.
500 */
501 public void addLayoutComponent(String name, Component child)
502 {
503 // You should not be adding stuff to this component.
504 // The contents are fixed.
505 }
506
507 /**
508 * This method configures the scrollbar's colors. This can be done by
509 * looking up the standard colors from the Look and Feel defaults.
510 */
511 protected void configureScrollBarColors()
512 {
513 trackColor = UIManager.getColor("ScrollBar.track");
514 trackHighlightColor = UIManager.getColor("ScrollBar.trackHighlight");
515 thumbColor = UIManager.getColor("ScrollBar.thumb");
516 thumbHighlightColor = UIManager.getColor("ScrollBar.thumbHighlight");
517 thumbDarkShadowColor = UIManager.getColor("ScrollBar.thumbDarkShadow");
518 thumbLightShadowColor = UIManager.getColor("ScrollBar.thumbShadow");
519 }
520
521 /**
522 * This method creates an ArrowButtonListener.
523 *
524 * @return A new ArrowButtonListener.
525 */
526 protected ArrowButtonListener createArrowButtonListener()
527 {
528 return new ArrowButtonListener();
529 }
530
531 /**
532 * This method creates a new JButton with the appropriate icon for the
533 * orientation.
534 *
535 * @param orientation The orientation this JButton uses.
536 *
537 * @return The increase JButton.
538 */
539 protected JButton createIncreaseButton(int orientation)
540 {
541 return new BasicArrowButton(orientation);
542 }
543
544 /**
545 * This method creates a new JButton with the appropriate icon for the
546 * orientation.
547 *
548 * @param orientation The orientation this JButton uses.
549 *
550 * @return The decrease JButton.
551 */
552 protected JButton createDecreaseButton(int orientation)
553 {
554 return new BasicArrowButton(orientation);
555 }
556
557 /**
558 * This method creates a new ModelListener.
559 *
560 * @return A new ModelListener.
561 */
562 protected ModelListener createModelListener()
563 {
564 return new ModelListener();
565 }
566
567 /**
568 * This method creates a new PropertyChangeListener.
569 *
570 * @return A new PropertyChangeListener.
571 */
572 protected PropertyChangeListener createPropertyChangeListener()
573 {
574 return new PropertyChangeHandler();
575 }
576
577 /**
578 * This method creates a new ScrollListener.
579 *
580 * @return A new ScrollListener.
581 */
582 protected ScrollListener createScrollListener()
583 {
584 return new ScrollListener();
585 }
586
587 /**
588 * This method creates a new TrackListener.
589 *
590 * @return A new TrackListener.
591 */
592 protected TrackListener createTrackListener()
593 {
594 return new TrackListener();
595 }
596
597 /**
598 * This method returns a new BasicScrollBarUI.
599 *
600 * @param c The JComponent to create a UI for.
601 *
602 * @return A new BasicScrollBarUI.
603 */
604 public static ComponentUI createUI(JComponent c)
605 {
606 return new BasicScrollBarUI();
607 }
608
609 /**
610 * This method returns the maximum size for this JComponent.
611 *
612 * @param c The JComponent to measure the maximum size for.
613 *
614 * @return The maximum size for the component.
615 */
616 public Dimension getMaximumSize(JComponent c)
617 {
618 return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
619 }
620
621 /**
622 * This method returns the maximum thumb size.
623 *
624 * @return The maximum thumb size.
625 */
626 protected Dimension getMaximumThumbSize()
627 {
628 return maximumThumbSize;
629 }
630
631 /**
632 * This method returns the minimum size for this JComponent.
633 *
634 * @param c The JComponent to measure the minimum size for.
635 *
636 * @return The minimum size for the component.
637 */
638 public Dimension getMinimumSize(JComponent c)
639 {
640 return getPreferredSize(c);
641 }
642
643 /**
644 * This method returns the minimum thumb size.
645 *
646 * @return The minimum thumb size.
647 */
648 protected Dimension getMinimumThumbSize()
649 {
650 return minimumThumbSize;
651 }
652
653 /**
654 * This method calculates the preferred size since calling
655 * getPreferredSize() returns a cached value.
656 * This is package-private to avoid an accessor method.
657 */
658 void calculatePreferredSize()
659 {
660 int height;
661 int width;
662 height = width = 0;
663
664 if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL)
665 {
666 width += incrButton.getPreferredSize().getWidth();
667 width += decrButton.getPreferredSize().getWidth();
668 width += 16;
669 height = UIManager.getInt("ScrollBar.width");
670 }
671 else
672 {
673 height += incrButton.getPreferredSize().getHeight();
674 height += decrButton.getPreferredSize().getHeight();
675 height += 16;
676 width = UIManager.getInt("ScrollBar.width");
677 }
678
679 Insets insets = scrollbar.getInsets();
680
681 height += insets.top + insets.bottom;
682 width += insets.left + insets.right;
683
684 preferredSize = new Dimension(width, height);
685 }
686
687 /**
688 * This method returns a cached value of the preferredSize. The only
689 * restrictions are: If the scrollbar is horizontal, the height should be
690 * the maximum of the height of the JButtons and the minimum width of the
691 * thumb. For vertical scrollbars, the calculation is similar (swap width
692 * for height and vice versa).
693 *
694 * @param c The JComponent to measure.
695 *
696 * @return The preferredSize.
697 */
698 public Dimension getPreferredSize(JComponent c)
699 {
700 calculatePreferredSize();
701 return preferredSize;
702 }
703
704 /**
705 * This method returns the thumb's bounds based on the current value of the
706 * scrollbar. This method updates the cached value and returns that.
707 *
708 * @return The thumb bounds.
709 */
710 protected Rectangle getThumbBounds()
711 {
712 return thumbRect;
713 }
714
715 /**
716 * This method calculates the bounds of the track. This method updates the
717 * cached value and returns it.
718 *
719 * @return The track's bounds.
720 */
721 protected Rectangle getTrackBounds()
722 {
723 return trackRect;
724 }
725
726 /**
727 * This method installs any addition Components that are a part of or
728 * related to this scrollbar.
729 */
730 protected void installComponents()
731 {
732 int orientation = scrollbar.getOrientation();
733 switch (orientation)
734 {
735 case JScrollBar.HORIZONTAL:
736 incrButton = createIncreaseButton(EAST);
737 decrButton = createDecreaseButton(WEST);
738 break;
739 default:
740 incrButton = createIncreaseButton(SOUTH);
741 decrButton = createDecreaseButton(NORTH);
742 break;
743 }
744
745 if (incrButton != null)
746 scrollbar.add(incrButton);
747 if (decrButton != null)
748 scrollbar.add(decrButton);
749 }
750
751 /**
752 * This method installs the defaults for the scrollbar specified by the
753 * Basic Look and Feel.
754 */
755 protected void installDefaults()
756 {
757 LookAndFeel.installColors(scrollbar, "ScrollBar.background",
758 "ScrollBar.foreground");
759 LookAndFeel.installBorder(scrollbar, "ScrollBar.border");
760 scrollbar.setOpaque(true);
761 scrollbar.setLayout(this);
762
763 configureScrollBarColors();
764
765 maximumThumbSize = UIManager.getDimension("ScrollBar.maximumThumbSize");
766 minimumThumbSize = UIManager.getDimension("ScrollBar.minimumThumbSize");
767 }
768
769 /**
770 * Installs the input map from the look and feel defaults, and a
771 * corresponding action map. Note the the keyboard bindings will only
772 * work when the {@link JScrollBar} component has the focus, which is rare.
773 */
774 protected void installKeyboardActions()
775 {
776 InputMap keyMap = getInputMap(
777 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
778 SwingUtilities.replaceUIInputMap(scrollbar,
779 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keyMap);
780 ActionMap map = getActionMap();
781 SwingUtilities.replaceUIActionMap(scrollbar, map);
782 }
783
784 /**
785 * Uninstalls the input map and action map installed by
786 * {@link #installKeyboardActions()}.
787 */
788 protected void uninstallKeyboardActions()
789 {
790 SwingUtilities.replaceUIActionMap(scrollbar, null);
791 SwingUtilities.replaceUIInputMap(scrollbar,
792 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
793 }
794
795 InputMap getInputMap(int condition)
796 {
797 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
798 return (InputMap) UIManager.get("ScrollBar.focusInputMap");
799 return null;
800 }
801
802 /**
803 * Returns the action map for the {@link JScrollBar}. All scroll bars
804 * share a single action map which is created the first time this method is
805 * called, then stored in the UIDefaults table for subsequent access.
806 *
807 * @return The shared action map.
808 */
809 ActionMap getActionMap()
810 {
811 ActionMap map = (ActionMap) UIManager.get("ScrollBar.actionMap");
812
813 if (map == null) // first time here
814 {
815 map = createActionMap();
816 if (map != null)
817 UIManager.put("ScrollBar.actionMap", map);
818 }
819 return map;
820 }
821
822 /**
823 * Creates the action map shared by all {@link JSlider} instances.
824 * This method is called once by {@link #getActionMap()} when it
825 * finds no action map in the UIDefaults table...after the map is
826 * created, it gets added to the defaults table so that subsequent
827 * calls to {@link #getActionMap()} will return the same shared
828 * instance.
829 *
830 * @return The action map.
831 */
832 ActionMap createActionMap()
833 {
834 ActionMap map = new ActionMapUIResource();
835 map.put("positiveUnitIncrement",
836 new AbstractAction("positiveUnitIncrement") {
837 public void actionPerformed(ActionEvent event)
838 {
839 JScrollBar sb = (JScrollBar) event.getSource();
840 if (sb.isVisible())
841 {
842 int delta = sb.getUnitIncrement(1);
843 sb.setValue(sb.getValue() + delta);
844 }
845 }
846 }
847 );
848 map.put("positiveBlockIncrement",
849 new AbstractAction("positiveBlockIncrement") {
850 public void actionPerformed(ActionEvent event)
851 {
852 JScrollBar sb = (JScrollBar) event.getSource();
853 if (sb.isVisible())
854 {
855 int delta = sb.getBlockIncrement(1);
856 sb.setValue(sb.getValue() + delta);
857 }
858 }
859 }
860 );
861 map.put("negativeUnitIncrement",
862 new AbstractAction("negativeUnitIncrement") {
863 public void actionPerformed(ActionEvent event)
864 {
865 JScrollBar sb = (JScrollBar) event.getSource();
866 if (sb.isVisible())
867 {
868 int delta = sb.getUnitIncrement(-1);
869 sb.setValue(sb.getValue() + delta);
870 }
871 }
872 }
873 );
874 map.put("negativeBlockIncrement",
875 new AbstractAction("negativeBlockIncrement") {
876 public void actionPerformed(ActionEvent event)
877 {
878 JScrollBar sb = (JScrollBar) event.getSource();
879 if (sb.isVisible())
880 {
881 int delta = sb.getBlockIncrement(-1);
882 sb.setValue(sb.getValue() + delta);
883 }
884 }
885 }
886 );
887 map.put("minScroll",
888 new AbstractAction("minScroll") {
889 public void actionPerformed(ActionEvent event)
890 {
891 JScrollBar sb = (JScrollBar) event.getSource();
892 if (sb.isVisible())
893 {
894 sb.setValue(sb.getMinimum());
895 }
896 }
897 }
898 );
899 map.put("maxScroll",
900 new AbstractAction("maxScroll") {
901 public void actionPerformed(ActionEvent event)
902 {
903 JScrollBar sb = (JScrollBar) event.getSource();
904 if (sb.isVisible())
905 {
906 sb.setValue(sb.getMaximum());
907 }
908 }
909 }
910 );
911 return map;
912 }
913
914 /**
915 * This method installs any listeners for the scrollbar. This method also
916 * installs listeners for things such as the JButtons and the timer.
917 */
918 protected void installListeners()
919 {
920 scrollListener = createScrollListener();
921 trackListener = createTrackListener();
922 buttonListener = createArrowButtonListener();
923 modelListener = createModelListener();
924 propertyChangeListener = createPropertyChangeListener();
925
926 scrollbar.addMouseMotionListener(trackListener);
927 scrollbar.addMouseListener(trackListener);
928
929 incrButton.addMouseListener(buttonListener);
930 decrButton.addMouseListener(buttonListener);
931
932 scrollbar.addPropertyChangeListener(propertyChangeListener);
933 scrollbar.getModel().addChangeListener(modelListener);
934
935 scrollTimer.addActionListener(scrollListener);
936 }
937
938 /**
939 * This method installs the UI for the component. This can include setting
940 * up listeners, defaults, and components. This also includes initializing
941 * any data objects.
942 *
943 * @param c The JComponent to install.
944 */
945 public void installUI(JComponent c)
946 {
947 super.installUI(c);
948 if (c instanceof JScrollBar)
949 {
950 scrollbar = (JScrollBar) c;
951
952 trackRect = new Rectangle();
953 thumbRect = new Rectangle();
954
955 scrollTimer = new Timer(300, null);
956
957 installDefaults();
958 installComponents();
959 configureScrollBarColors();
960 installListeners();
961 installKeyboardActions();
962
963 calculatePreferredSize();
964 }
965 }
966
967 /**
968 * This method lays out the scrollbar.
969 *
970 * @param scrollbarContainer The Container to layout.
971 */
972 public void layoutContainer(Container scrollbarContainer)
973 {
974 if (scrollbarContainer instanceof JScrollBar)
975 {
976 if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL)
977 layoutHScrollbar((JScrollBar) scrollbarContainer);
978 else
979 layoutVScrollbar((JScrollBar) scrollbarContainer);
980 }
981 }
982
983 /**
984 * This method lays out the scrollbar horizontally.
985 *
986 * @param sb The JScrollBar to layout.
987 */
988 protected void layoutHScrollbar(JScrollBar sb)
989 {
990 Rectangle vr = new Rectangle();
991 SwingUtilities.calculateInnerArea(scrollbar, vr);
992
993 Dimension incrDims = incrButton.getPreferredSize();
994 Dimension decrDims = decrButton.getPreferredSize();
995
996 // calculate and update the track bounds
997 SwingUtilities.calculateInnerArea(scrollbar, trackRect);
998 trackRect.width -= incrDims.getWidth();
999 trackRect.width -= decrDims.getWidth();
1000 trackRect.x += decrDims.getWidth();
1001
1002 updateThumbRect();
1003
1004 decrButton.setBounds(vr.x, vr.y, decrDims.width, trackRect.height);
1005 incrButton.setBounds(trackRect.x + trackRect.width, vr.y, incrDims.width,
1006 trackRect.height);
1007 }
1008
1009 /**
1010 * This method lays out the scrollbar vertically.
1011 *
1012 * @param sb The JScrollBar to layout.
1013 */
1014 protected void layoutVScrollbar(JScrollBar sb)
1015 {
1016 Rectangle vr = new Rectangle();
1017 SwingUtilities.calculateInnerArea(scrollbar, vr);
1018
1019 Dimension incrDims = incrButton.getPreferredSize();
1020 Dimension decrDims = decrButton.getPreferredSize();
1021
1022 // Update rectangles
1023 SwingUtilities.calculateInnerArea(scrollbar, trackRect);
1024 trackRect.height -= incrDims.getHeight();
1025 trackRect.height -= decrDims.getHeight();
1026 trackRect.y += decrDims.getHeight();
1027
1028 updateThumbRect();
1029
1030 decrButton.setBounds(vr.x, vr.y, trackRect.width, decrDims.height);
1031 incrButton.setBounds(vr.x, trackRect.y + trackRect.height,
1032 trackRect.width, incrDims.height);
1033 }
1034
1035 /**
1036 * Updates the thumb rect.
1037 */
1038 void updateThumbRect()
1039 {
1040 int max = scrollbar.getMaximum();
1041 int min = scrollbar.getMinimum();
1042 int value = scrollbar.getValue();
1043 int extent = scrollbar.getVisibleAmount();
1044 if (max - extent <= min)
1045 {
1046 if (scrollbar.getOrientation() == JScrollBar.HORIZONTAL)
1047 {
1048 thumbRect.x = trackRect.x;
1049 thumbRect.y = trackRect.y;
1050 thumbRect.width = getMinimumThumbSize().width;
1051 thumbRect.height = trackRect.height;
1052 }
1053 else
1054 {
1055 thumbRect.x = trackRect.x;
1056 thumbRect.y = trackRect.y;
1057 thumbRect.width = trackRect.width;
1058 thumbRect.height = getMinimumThumbSize().height;
1059 }
1060 }
1061 else
1062 {
1063 if (scrollbar.getOrientation() == JScrollBar.HORIZONTAL)
1064 {
1065 thumbRect.x = trackRect.x;
1066 thumbRect.width = Math.max(extent * trackRect.width / (max - min),
1067 getMinimumThumbSize().width);
1068 int availableWidth = trackRect.width - thumbRect.width;
1069 thumbRect.x += (value - min) * availableWidth / (max - min - extent);
1070 thumbRect.y = trackRect.y;
1071 thumbRect.height = trackRect.height;
1072 }
1073 else
1074 {
1075 thumbRect.x = trackRect.x;
1076 thumbRect.height = Math.max(extent * trackRect.height / (max - min),
1077 getMinimumThumbSize().height);
1078 int availableHeight = trackRect.height - thumbRect.height;
1079 thumbRect.y = trackRect.y
1080 + (value - min) * availableHeight / (max - min - extent);
1081 thumbRect.width = trackRect.width;
1082 }
1083 }
1084
1085 }
1086
1087 /**
1088 * This method returns the minimum size required for the layout.
1089 *
1090 * @param scrollbarContainer The Container that is laid out.
1091 *
1092 * @return The minimum size.
1093 */
1094 public Dimension minimumLayoutSize(Container scrollbarContainer)
1095 {
1096 return preferredLayoutSize(scrollbarContainer);
1097 }
1098
1099 /**
1100 * This method is called when the component is painted.
1101 *
1102 * @param g The Graphics object to paint with.
1103 * @param c The JComponent to paint.
1104 */
1105 public void paint(Graphics g, JComponent c)
1106 {
1107 paintTrack(g, c, getTrackBounds());
1108 paintThumb(g, c, getThumbBounds());
1109
1110 if (trackHighlight == INCREASE_HIGHLIGHT)
1111 paintIncreaseHighlight(g);
1112 else if (trackHighlight == DECREASE_HIGHLIGHT)
1113 paintDecreaseHighlight(g);
1114 }
1115
1116 /**
1117 * This method is called when repainting and the mouse is pressed in the
1118 * track. It paints the track below the thumb with the trackHighlight
1119 * color.
1120 *
1121 * @param g The Graphics object to paint with.
1122 */
1123 protected void paintDecreaseHighlight(Graphics g)
1124 {
1125 Color saved = g.getColor();
1126
1127 g.setColor(trackHighlightColor);
1128 if (scrollbar.getOrientation() == HORIZONTAL)
1129 g.fillRect(trackRect.x, trackRect.y, thumbRect.x - trackRect.x,
1130 trackRect.height);
1131 else
1132 g.fillRect(trackRect.x, trackRect.y, trackRect.width,
1133 thumbRect.y - trackRect.y);
1134 g.setColor(saved);
1135 }
1136
1137 /**
1138 * This method is called when repainting and the mouse is pressed in the
1139 * track. It paints the track above the thumb with the trackHighlight
1140 * color.
1141 *
1142 * @param g The Graphics objet to paint with.
1143 */
1144 protected void paintIncreaseHighlight(Graphics g)
1145 {
1146 Color saved = g.getColor();
1147
1148 g.setColor(trackHighlightColor);
1149 if (scrollbar.getOrientation() == HORIZONTAL)
1150 g.fillRect(thumbRect.x + thumbRect.width, trackRect.y,
1151 trackRect.x + trackRect.width - thumbRect.x - thumbRect.width,
1152 trackRect.height);
1153 else
1154 g.fillRect(trackRect.x, thumbRect.y + thumbRect.height, trackRect.width,
1155 trackRect.y + trackRect.height - thumbRect.y
1156 - thumbRect.height);
1157 g.setColor(saved);
1158 }
1159
1160 /**
1161 * This method paints the thumb.
1162 *
1163 * @param g The Graphics object to paint with.
1164 * @param c The Component that is being painted.
1165 * @param thumbBounds The thumb bounds.
1166 */
1167 protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds)
1168 {
1169 g.setColor(thumbColor);
1170 g.fillRect(thumbBounds.x, thumbBounds.y, thumbBounds.width,
1171 thumbBounds.height);
1172
1173 BasicGraphicsUtils.drawBezel(g, thumbBounds.x, thumbBounds.y,
1174 thumbBounds.width, thumbBounds.height,
1175 false, false, thumbDarkShadowColor,
1176 thumbDarkShadowColor, thumbHighlightColor,
1177 thumbHighlightColor);
1178 }
1179
1180 /**
1181 * This method paints the track.
1182 *
1183 * @param g The Graphics object to paint with.
1184 * @param c The JComponent being painted.
1185 * @param trackBounds The track's bounds.
1186 */
1187 protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds)
1188 {
1189 Color saved = g.getColor();
1190 g.setColor(trackColor);
1191 g.fill3DRect(trackBounds.x, trackBounds.y, trackBounds.width,
1192 trackBounds.height, false);
1193 g.setColor(saved);
1194 }
1195
1196 /**
1197 * This method returns the preferred size for the layout.
1198 *
1199 * @param scrollbarContainer The Container to find a size for.
1200 *
1201 * @return The preferred size for the layout.
1202 */
1203 public Dimension preferredLayoutSize(Container scrollbarContainer)
1204 {
1205 if (scrollbarContainer instanceof JComponent)
1206 return getPreferredSize((JComponent) scrollbarContainer);
1207 else
1208 return null;
1209 }
1210
1211 /**
1212 * This method removes a child component from the layout.
1213 *
1214 * @param child The child to remove.
1215 */
1216 public void removeLayoutComponent(Component child)
1217 {
1218 // You should not be removing stuff from this component.
1219 }
1220
1221 /**
1222 * The method scrolls the thumb by a block in the direction specified.
1223 *
1224 * @param direction The direction to scroll.
1225 */
1226 protected void scrollByBlock(int direction)
1227 {
1228 scrollByBlock(scrollbar, direction);
1229 }
1230
1231 /**
1232 * Scrolls the specified <code>scrollBar</code> by one block (according
1233 * to the scrollable protocol) in the specified <code>direction</code>.
1234 *
1235 * This method is here statically to support wheel scrolling from the
1236 * BasicScrollPaneUI without code duplication.
1237 *
1238 * @param scrollBar the scrollbar to scroll
1239 * @param direction the scroll direction
1240 */
1241 static final void scrollByBlock(JScrollBar scrollBar, int direction)
1242 {
1243 int delta;
1244 if (direction > 0)
1245 delta = scrollBar.getBlockIncrement(direction);
1246 else
1247 delta = - scrollBar.getBlockIncrement(direction);
1248 int oldValue = scrollBar.getValue();
1249 int newValue = oldValue + delta;
1250
1251 // Overflow check.
1252 if (delta > 0 && newValue < oldValue)
1253 newValue = scrollBar.getMaximum();
1254 else if (delta < 0 && newValue > oldValue)
1255 newValue = scrollBar.getMinimum();
1256
1257 scrollBar.setValue(newValue);
1258 }
1259
1260 /**
1261 * The method scrolls the thumb by a unit in the direction specified.
1262 *
1263 * @param direction The direction to scroll.
1264 */
1265 protected void scrollByUnit(int direction)
1266 {
1267 scrollByUnits(scrollbar, direction, 1);
1268 }
1269
1270 /**
1271 * Scrolls the specified <code>scrollbac/code> by <code>units</code> units
1272 * in the specified <code>direction</code>.
1273 *
1274 * This method is here statically to support wheel scrolling from the
1275 * BasicScrollPaneUI without code duplication.
1276 *
1277 * @param scrollBar the scrollbar to scroll
1278 * @param direction the direction
1279 * @param units the number of units to scroll
1280 */
1281 static final void scrollByUnits(JScrollBar scrollBar, int direction,
1282 int units)
1283 {
1284 // Do this inside a loop so that we don't clash with the scrollable
1285 // interface, which can return different units at times. For instance,
1286 // a Scrollable could return a unit of 2 pixels only to adjust the
1287 // visibility of an item. If we would simply multiply this by units,
1288 // then we would only get 6 pixels, which is complete crap.
1289 for (int i = 0; i < units; i++)
1290 {
1291 int delta;
1292 if (direction > 0)
1293 delta = scrollBar.getUnitIncrement(direction);
1294 else
1295 delta = - scrollBar.getUnitIncrement(direction);
1296 int oldValue = scrollBar.getValue();
1297 int newValue = oldValue + delta;
1298
1299 // Overflow check.
1300 if (delta > 0 && newValue < oldValue)
1301 newValue = scrollBar.getMaximum();
1302 else if (delta < 0 && newValue > oldValue)
1303 newValue = scrollBar.getMinimum();
1304
1305 scrollBar.setValue(newValue);
1306 }
1307 }
1308
1309 /**
1310 * This method sets the thumb's bounds.
1311 *
1312 * @param x The X position of the thumb.
1313 * @param y The Y position of the thumb.
1314 * @param width The width of the thumb.
1315 * @param height The height of the thumb.
1316 */
1317 protected void setThumbBounds(int x, int y, int width, int height)
1318 {
1319 thumbRect.x = x;
1320 thumbRect.y = y;
1321 thumbRect.width = width;
1322 thumbRect.height = height;
1323 }
1324
1325 /**
1326 * This method uninstalls any components that are a part of or related to
1327 * this scrollbar.
1328 */
1329 protected void uninstallComponents()
1330 {
1331 if (incrButton != null)
1332 scrollbar.remove(incrButton);
1333 if (decrButton != null)
1334 scrollbar.remove(decrButton);
1335 }
1336
1337 /**
1338 * This method uninstalls any defaults that this scrollbar acquired from the
1339 * Basic Look and Feel defaults.
1340 */
1341 protected void uninstallDefaults()
1342 {
1343 scrollbar.setForeground(null);
1344 scrollbar.setBackground(null);
1345 LookAndFeel.uninstallBorder(scrollbar);
1346 incrButton = null;
1347 decrButton = null;
1348 }
1349
1350 /**
1351 * This method uninstalls any listeners that were registered during install.
1352 */
1353 protected void uninstallListeners()
1354 {
1355 if (scrollTimer != null)
1356 scrollTimer.removeActionListener(scrollListener);
1357
1358 if (scrollbar != null)
1359 {
1360 scrollbar.getModel().removeChangeListener(modelListener);
1361 scrollbar.removePropertyChangeListener(propertyChangeListener);
1362 scrollbar.removeMouseListener(trackListener);
1363 scrollbar.removeMouseMotionListener(trackListener);
1364 }
1365
1366 if (decrButton != null)
1367 decrButton.removeMouseListener(buttonListener);
1368 if (incrButton != null)
1369 incrButton.removeMouseListener(buttonListener);
1370
1371 propertyChangeListener = null;
1372 modelListener = null;
1373 buttonListener = null;
1374 trackListener = null;
1375 scrollListener = null;
1376 }
1377
1378 /**
1379 * This method uninstalls the UI. This includes removing any defaults,
1380 * listeners, and components that this UI may have initialized. It also
1381 * nulls any instance data.
1382 *
1383 * @param c The Component to uninstall for.
1384 */
1385 public void uninstallUI(JComponent c)
1386 {
1387 uninstallKeyboardActions();
1388 uninstallListeners();
1389 uninstallDefaults();
1390 uninstallComponents();
1391
1392 scrollTimer = null;
1393
1394 thumbRect = null;
1395 trackRect = null;
1396
1397 trackColor = null;
1398 trackHighlightColor = null;
1399 thumbColor = null;
1400 thumbHighlightColor = null;
1401 thumbDarkShadowColor = null;
1402 thumbLightShadowColor = null;
1403
1404 scrollbar = null;
1405 }
1406
1407 /**
1408 * This method returns the value in the scrollbar's range given the y
1409 * coordinate. If the value is out of range, it will return the closest
1410 * legal value.
1411 * This is package-private to avoid an accessor method.
1412 *
1413 * @param yPos The y coordinate to calculate a value for.
1414 *
1415 * @return The value for the y coordinate.
1416 */
1417 int valueForYPosition(int yPos)
1418 {
1419 int min = scrollbar.getMinimum();
1420 int max = scrollbar.getMaximum();
1421 int len = trackRect.height;
1422
1423 int value;
1424
1425 // If the length is 0, you shouldn't be able to even see where the thumb is.
1426 // This really shouldn't ever happen, but just in case, we'll return the middle.
1427 if (len == 0)
1428 return (max - min) / 2;
1429
1430 value = (yPos - trackRect.y) * (max - min) / len + min;
1431
1432 // If this isn't a legal value, then we'll have to move to one now.
1433 if (value > max)
1434 value = max;
1435 else if (value < min)
1436 value = min;
1437 return value;
1438 }
1439
1440 /**
1441 * This method returns the value in the scrollbar's range given the x
1442 * coordinate. If the value is out of range, it will return the closest
1443 * legal value.
1444 * This is package-private to avoid an accessor method.
1445 *
1446 * @param xPos The x coordinate to calculate a value for.
1447 *
1448 * @return The value for the x coordinate.
1449 */
1450 int valueForXPosition(int xPos)
1451 {
1452 int min = scrollbar.getMinimum();
1453 int max = scrollbar.getMaximum();
1454 int len = trackRect.width;
1455
1456 int value;
1457
1458 // If the length is 0, you shouldn't be able to even see where the slider is.
1459 // This really shouldn't ever happen, but just in case, we'll return the middle.
1460 if (len == 0)
1461 return (max - min) / 2;
1462
1463 value = (xPos - trackRect.x) * (max - min) / len + min;
1464
1465 // If this isn't a legal value, then we'll have to move to one now.
1466 if (value > max)
1467 value = max;
1468 else if (value < min)
1469 value = min;
1470 return value;
1471 }
1472
1473 /**
1474 * Returns true if the mouse is over the thumb.
1475 *
1476 * @return true if the mouse is over the thumb.
1477 *
1478 * @since 1.5
1479 */
1480 public boolean isThumbRollover()
1481 {
1482 return thumbRollover;
1483 }
1484
1485 /**
1486 * Set thumbRollover to active. This indicates
1487 * whether or not the mouse is over the thumb.
1488 *
1489 * @param active - true if the mouse is over the thumb.
1490 *
1491 * @since 1.5
1492 */
1493 protected void setThumbRollover(boolean active)
1494 {
1495 thumbRollover = active;
1496 }
1497
1498 /**
1499 * Indicates whether the user can position the thumb with
1500 * a mouse click (i.e. middle button).
1501 *
1502 * @return true if the user can position the thumb with a mouse
1503 * click.
1504 *
1505 * @since 1.5
1506 */
1507 public boolean getSupportsAbsolutePositioning()
1508 {
1509 // The positioning feature has not been implemented.
1510 // So, false is always returned.
1511 return false;
1512 }
1513 }