001 /* BasicSliderUI.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.Dimension;
044 import java.awt.Graphics;
045 import java.awt.Insets;
046 import java.awt.Point;
047 import java.awt.Polygon;
048 import java.awt.Rectangle;
049 import java.awt.event.ActionEvent;
050 import java.awt.event.ActionListener;
051 import java.awt.event.ComponentAdapter;
052 import java.awt.event.ComponentEvent;
053 import java.awt.event.ComponentListener;
054 import java.awt.event.FocusEvent;
055 import java.awt.event.FocusListener;
056 import java.awt.event.MouseEvent;
057 import java.beans.PropertyChangeEvent;
058 import java.beans.PropertyChangeListener;
059 import java.util.Dictionary;
060 import java.util.Enumeration;
061
062 import javax.swing.AbstractAction;
063 import javax.swing.ActionMap;
064 import javax.swing.BoundedRangeModel;
065 import javax.swing.InputMap;
066 import javax.swing.JComponent;
067 import javax.swing.JSlider;
068 import javax.swing.LookAndFeel;
069 import javax.swing.SwingUtilities;
070 import javax.swing.Timer;
071 import javax.swing.UIManager;
072 import javax.swing.event.ChangeEvent;
073 import javax.swing.event.ChangeListener;
074 import javax.swing.event.MouseInputAdapter;
075 import javax.swing.plaf.ActionMapUIResource;
076 import javax.swing.plaf.ComponentUI;
077 import javax.swing.plaf.SliderUI;
078
079 /**
080 * <p>
081 * BasicSliderUI.java This is the UI delegate in the Basic look and feel that
082 * paints JSliders.
083 * </p>
084 *
085 * <p>
086 * The UI delegate keeps track of 6 rectangles that place the various parts of
087 * the JSlider inside the component.
088 * </p>
089 *
090 * <p>
091 * The rectangles are organized as follows:
092 * </p>
093 * <pre>
094 * +-------------------------------------------------------+ <-- focusRect
095 * | |
096 * | +==+-------------------+==+--------------------+==+<------ contentRect
097 * | | | | |<---thumbRect | | |
098 * | | | TRACK | | |<--------- trackRect
099 * | | +-------------------+==+--------------------+ | |
100 * | | | | | |
101 * | | | TICKS GO HERE |<-------- tickRect
102 * | | | | | |
103 * | +==+-------------------------------------------+==+ |
104 * | | | | | |
105 * | | | | |<----- labelRect
106 * | | | LABELS GO HERE | | |
107 * | | | | | |
108 * | | | | | |
109 * | | | | | |
110 * | | | | | |
111 * | | | | |
112 * </pre>
113 *
114 * <p>
115 * The space between the contentRect and the focusRect are the FocusInsets.
116 * </p>
117 *
118 * <p>
119 * The space between the focusRect and the component bounds is the insetCache
120 * which are the component's insets.
121 * </p>
122 *
123 * <p>
124 * The top of the thumb is the top of the contentRect. The trackRect has to be
125 * as tall as the thumb.
126 * </p>
127 *
128 * <p>
129 * The trackRect and tickRect do not start from the left edge of the
130 * focusRect. They are trackBuffer away from each side of the focusRect. This
131 * is so that the thumb has room to move.
132 * </p>
133 *
134 * <p>
135 * The labelRect does start right against the contentRect's left and right
136 * edges and it gets all remaining space.
137 * </p>
138 */
139 public class BasicSliderUI extends SliderUI
140 {
141 /**
142 * Helper class that listens to the {@link JSlider}'s model for changes.
143 *
144 * @specnote Apparently this class was intended to be protected,
145 * but was made public by a compiler bug and is now
146 * public for compatibility.
147 */
148 public class ChangeHandler implements ChangeListener
149 {
150 /**
151 * Called when the slider's model has been altered. The UI delegate should
152 * recalculate any rectangles that are dependent on the model for their
153 * positions and repaint.
154 *
155 * @param e A static {@link ChangeEvent} passed from the model.
156 */
157 public void stateChanged(ChangeEvent e)
158 {
159 // Maximum, minimum, and extent values will be taken
160 // care of automatically when the slider is repainted.
161 // Only thing that needs recalculation is the thumb.
162 calculateThumbLocation();
163 slider.repaint();
164 }
165 }
166
167 /**
168 * Helper class that listens for resize events.
169 *
170 * @specnote Apparently this class was intended to be protected,
171 * but was made public by a compiler bug and is now
172 * public for compatibility.
173 */
174 public class ComponentHandler extends ComponentAdapter
175 {
176 /**
177 * Called when the size of the component changes. The UI delegate should
178 * recalculate any rectangles that are dependent on the model for their
179 * positions and repaint.
180 *
181 * @param e A {@link ComponentEvent}.
182 */
183 public void componentResized(ComponentEvent e)
184 {
185 calculateGeometry();
186 slider.repaint();
187 }
188 }
189
190 /**
191 * Helper class that listens for focus events.
192 *
193 * @specnote Apparently this class was intended to be protected,
194 * but was made public by a compiler bug and is now
195 * public for compatibility.
196 */
197 public class FocusHandler implements FocusListener
198 {
199 /**
200 * Called when the {@link JSlider} has gained focus. It should repaint
201 * the slider with the focus drawn.
202 *
203 * @param e A {@link FocusEvent}.
204 */
205 public void focusGained(FocusEvent e)
206 {
207 slider.repaint();
208 }
209
210 /**
211 * Called when the {@link JSlider} has lost focus. It should repaint the
212 * slider without the focus drawn.
213 *
214 * @param e A {@link FocusEvent}.
215 */
216 public void focusLost(FocusEvent e)
217 {
218 slider.repaint();
219 }
220 }
221
222 /**
223 * Helper class that listens for changes to the properties of the {@link
224 * JSlider}.
225 */
226 public class PropertyChangeHandler implements PropertyChangeListener
227 {
228 /**
229 * Called when one of the properties change. The UI should recalculate any
230 * rectangles if necessary and repaint.
231 *
232 * @param e A {@link PropertyChangeEvent}.
233 */
234 public void propertyChange(PropertyChangeEvent e)
235 {
236 // Check for orientation changes.
237 String prop = e.getPropertyName();
238 if (prop.equals("orientation")
239 || prop.equals("inverted")
240 || prop.equals("labelTable")
241 || prop.equals("majorTickSpacing")
242 || prop.equals("minorTickSpacing")
243 || prop.equals("paintTicks")
244 || prop.equals("paintTrack")
245 || prop.equals("paintLabels"))
246 {
247 calculateGeometry();
248 slider.repaint();
249 }
250 else if (e.getPropertyName().equals("model"))
251 {
252 BoundedRangeModel oldModel = (BoundedRangeModel) e.getOldValue();
253 oldModel.removeChangeListener(changeListener);
254 slider.getModel().addChangeListener(changeListener);
255 calculateThumbLocation();
256 slider.repaint();
257 }
258 }
259 }
260
261 /**
262 * Helper class that listens to our swing timer. This class is responsible
263 * for listening to the timer and moving the thumb in the proper direction
264 * every interval.
265 *
266 * @specnote Apparently this class was intended to be protected,
267 * but was made public by a compiler bug and is now
268 * public for compatibility.
269 */
270 public class ScrollListener implements ActionListener
271 {
272 /** Indicates which direction the thumb should scroll. */
273 private transient int direction;
274
275 /** Indicates whether we should scroll in blocks or in units. */
276 private transient boolean block;
277
278 /**
279 * Creates a new ScrollListener object.
280 */
281 public ScrollListener()
282 {
283 direction = POSITIVE_SCROLL;
284 block = false;
285 }
286
287 /**
288 * Creates a new ScrollListener object.
289 *
290 * @param dir The direction to scroll in.
291 * @param block If movement will be in blocks.
292 */
293 public ScrollListener(int dir, boolean block)
294 {
295 direction = dir;
296 this.block = block;
297 }
298
299 /**
300 * Called every time the swing timer reaches its interval. If the thumb
301 * needs to move, then this method will move the thumb one block or unit
302 * in the direction desired. Otherwise, the timer can be stopped.
303 *
304 * @param e An {@link ActionEvent}.
305 */
306 public void actionPerformed(ActionEvent e)
307 {
308 if (! trackListener.shouldScroll(direction))
309 {
310 scrollTimer.stop();
311 return;
312 }
313
314 if (block)
315 scrollByBlock(direction);
316 else
317 scrollByUnit(direction);
318 }
319
320 /**
321 * Sets the direction to scroll in.
322 *
323 * @param direction The direction to scroll in.
324 */
325 public void setDirection(int direction)
326 {
327 this.direction = direction;
328 }
329
330 /**
331 * Sets whether movement will be in blocks.
332 *
333 * @param block If movement will be in blocks.
334 */
335 public void setScrollByBlock(boolean block)
336 {
337 this.block = block;
338 }
339 }
340
341 /**
342 * Helper class that listens for mouse events.
343 *
344 * @specnote Apparently this class was intended to be protected,
345 * but was made public by a compiler bug and is now
346 * public for compatibility.
347 */
348 public class TrackListener extends MouseInputAdapter
349 {
350 /** The current X position of the mouse. */
351 protected int currentMouseX;
352
353 /** The current Y position of the mouse. */
354 protected int currentMouseY;
355
356 /**
357 * The offset between the current slider value and the cursor's position.
358 */
359 protected int offset;
360
361 /**
362 * Called when the mouse has been dragged. This should find the mouse's
363 * current position and adjust the value of the {@link JSlider}
364 * accordingly.
365 *
366 * @param e A {@link MouseEvent}
367 */
368 public void mouseDragged(MouseEvent e)
369 {
370 dragging = true;
371 if (slider.isEnabled())
372 {
373 currentMouseX = e.getX();
374 currentMouseY = e.getY();
375 if (slider.getValueIsAdjusting())
376 {
377 int value;
378 if (slider.getOrientation() == JSlider.HORIZONTAL)
379 value = valueForXPosition(currentMouseX) - offset;
380 else
381 value = valueForYPosition(currentMouseY) - offset;
382
383 slider.setValue(value);
384 }
385 }
386 }
387
388 /**
389 * Called when the mouse has moved over a component but no buttons have
390 * been pressed yet.
391 *
392 * @param e A {@link MouseEvent}
393 */
394 public void mouseMoved(MouseEvent e)
395 {
396 // Don't care that we're moved unless we're dragging.
397 }
398
399 /**
400 * Called when the mouse is pressed. When the press occurs on the thumb
401 * itself, the {@link JSlider} should have its value set to where the
402 * mouse was pressed. If the press occurs on the track, then the thumb
403 * should move one block towards the direction of the mouse.
404 *
405 * @param e A {@link MouseEvent}
406 */
407 public void mousePressed(MouseEvent e)
408 {
409 if (slider.isEnabled())
410 {
411 currentMouseX = e.getX();
412 currentMouseY = e.getY();
413
414 int value;
415 if (slider.getOrientation() == JSlider.HORIZONTAL)
416 value = valueForXPosition(currentMouseX);
417 else
418 value = valueForYPosition(currentMouseY);
419
420 if (slider.getSnapToTicks())
421 value = findClosestTick(value);
422
423 // If the thumb is hit, then we don't need to set the timers to
424 // move it.
425 if (! thumbRect.contains(e.getPoint()))
426 {
427 // The mouse has hit some other part of the slider.
428 // The value moves no matter where in the slider you hit.
429 if (value > slider.getValue())
430 scrollDueToClickInTrack(POSITIVE_SCROLL);
431 else
432 scrollDueToClickInTrack(NEGATIVE_SCROLL);
433 }
434 else
435 {
436 slider.setValueIsAdjusting(true);
437 offset = value - slider.getValue();
438 }
439 }
440 }
441
442 /**
443 * Called when the mouse is released. This should stop the timer that
444 * scrolls the thumb.
445 *
446 * @param e A {@link MouseEvent}
447 */
448 public void mouseReleased(MouseEvent e)
449 {
450 dragging = false;
451 if (slider.isEnabled())
452 {
453 currentMouseX = e.getX();
454 currentMouseY = e.getY();
455
456 if (slider.getValueIsAdjusting())
457 {
458 slider.setValueIsAdjusting(false);
459 if (slider.getSnapToTicks())
460 slider.setValue(findClosestTick(slider.getValue()));
461 }
462 if (scrollTimer != null)
463 scrollTimer.stop();
464 }
465 slider.repaint();
466 }
467
468 /**
469 * Indicates whether the thumb should scroll in the given direction.
470 *
471 * @param direction The direction to check.
472 *
473 * @return True if the thumb should move in that direction.
474 */
475 public boolean shouldScroll(int direction)
476 {
477 int value;
478 if (slider.getOrientation() == JSlider.HORIZONTAL)
479 value = valueForXPosition(currentMouseX);
480 else
481 value = valueForYPosition(currentMouseY);
482
483 if (direction == POSITIVE_SCROLL)
484 return value > slider.getValue();
485 else
486 return value < slider.getValue();
487 }
488 }
489
490 /**
491 * This class is no longer used as of JDK1.3.
492 */
493 public class ActionScroller extends AbstractAction
494 {
495 /**
496 * Not used.
497 *
498 * @param slider not used
499 * @param dir not used
500 * @param block not used
501 */
502 public ActionScroller(JSlider slider, int dir, boolean block)
503 {
504 // Not used.
505 }
506
507 /**
508 * Not used.
509 *
510 * @param event not used
511 */
512 public void actionPerformed(ActionEvent event)
513 {
514 // Not used.
515 }
516 }
517
518 /** Listener for changes from the model. */
519 protected ChangeListener changeListener;
520
521 /** Listener for changes to the {@link JSlider}. */
522 protected PropertyChangeListener propertyChangeListener;
523
524 /** Listener for the scrollTimer. */
525 protected ScrollListener scrollListener;
526
527 /** Listener for component resizing. */
528 protected ComponentListener componentListener;
529
530 /** Listener for focus handling. */
531 protected FocusListener focusListener;
532
533 /** Listener for mouse events. */
534 protected TrackListener trackListener;
535
536 /** The insets between the FocusRectangle and the ContentRectangle. */
537 protected Insets focusInsets;
538
539 /** The {@link JSlider}'s insets. */
540 protected Insets insetCache;
541
542 /** Rectangle describing content bounds. See diagram above. */
543 protected Rectangle contentRect;
544
545 /** Rectangle describing focus bounds. See diagram above. */
546 protected Rectangle focusRect;
547
548 /** Rectangle describing the thumb's bounds. See diagram above. */
549 protected Rectangle thumbRect;
550
551 /** Rectangle describing the tick bounds. See diagram above. */
552 protected Rectangle tickRect;
553
554 /** Rectangle describing the label bounds. See diagram above. */
555 protected Rectangle labelRect;
556
557 /** Rectangle describing the track bounds. See diagram above. */
558 protected Rectangle trackRect;
559
560 /** FIXME: use this somewhere. */
561 public static final int MAX_SCROLL = 2;
562
563 /** FIXME: use this somewhere. */
564 public static final int MIN_SCROLL = -2;
565
566 /** A constant describing scrolling towards the minimum. */
567 public static final int NEGATIVE_SCROLL = -1;
568
569 /** A constant describing scrolling towards the maximum. */
570 public static final int POSITIVE_SCROLL = 1;
571
572 /** The gap between the edges of the contentRect and trackRect. */
573 protected int trackBuffer;
574
575 /** Whether this slider is actually drawn left to right. */
576 protected boolean leftToRightCache;
577
578 /** A timer that periodically moves the thumb. */
579 protected Timer scrollTimer;
580
581 /** A reference to the {@link JSlider} that this UI was created for. */
582 protected JSlider slider;
583
584 /** The shadow color. */
585 private transient Color shadowColor;
586
587 /** The highlight color. */
588 private transient Color highlightColor;
589
590 /** The focus color. */
591 private transient Color focusColor;
592
593 /** True if the user is dragging the slider. */
594 boolean dragging;
595
596 /**
597 * Creates a new Basic look and feel Slider UI.
598 *
599 * @param b The {@link JSlider} that this UI was created for.
600 */
601 public BasicSliderUI(JSlider b)
602 {
603 super();
604 }
605
606 /**
607 * Returns true if the user is dragging the slider.
608 *
609 * @return true if the slider is being dragged.
610 *
611 * @since 1.5
612 */
613 protected boolean isDragging()
614 {
615 return dragging;
616 }
617
618 /**
619 * Gets the shadow color to be used for this slider. The shadow color is the
620 * color used for drawing the top and left edges of the track.
621 *
622 * @return The shadow color.
623 */
624 protected Color getShadowColor()
625 {
626 return shadowColor;
627 }
628
629 /**
630 * Gets the highlight color to be used for this slider. The highlight color
631 * is the color used for drawing the bottom and right edges of the track.
632 *
633 * @return The highlight color.
634 */
635 protected Color getHighlightColor()
636 {
637 return highlightColor;
638 }
639
640 /**
641 * Gets the focus color to be used for this slider. The focus color is the
642 * color used for drawing the focus rectangle when the component gains
643 * focus.
644 *
645 * @return The focus color.
646 */
647 protected Color getFocusColor()
648 {
649 return focusColor;
650 }
651
652 /**
653 * Factory method to create a BasicSliderUI for the given {@link
654 * JComponent}, which should be a {@link JSlider}.
655 *
656 * @param b The {@link JComponent} a UI is being created for.
657 *
658 * @return A BasicSliderUI for the {@link JComponent}.
659 */
660 public static ComponentUI createUI(JComponent b)
661 {
662 return new BasicSliderUI((JSlider) b);
663 }
664
665 /**
666 * Installs and initializes all fields for this UI delegate. Any properties
667 * of the UI that need to be initialized and/or set to defaults will be
668 * done now. It will also install any listeners necessary.
669 *
670 * @param c The {@link JComponent} that is having this UI installed.
671 */
672 public void installUI(JComponent c)
673 {
674 super.installUI(c);
675 if (c instanceof JSlider)
676 {
677 slider = (JSlider) c;
678
679 focusRect = new Rectangle();
680 contentRect = new Rectangle();
681 thumbRect = new Rectangle();
682 trackRect = new Rectangle();
683 tickRect = new Rectangle();
684 labelRect = new Rectangle();
685
686 insetCache = slider.getInsets();
687 leftToRightCache = ! slider.getInverted();
688
689 scrollTimer = new Timer(200, null);
690 scrollTimer.setRepeats(true);
691
692 installDefaults(slider);
693 installListeners(slider);
694 installKeyboardActions(slider);
695
696 calculateFocusRect();
697
698 calculateContentRect();
699 calculateThumbSize();
700 calculateTrackBuffer();
701 calculateTrackRect();
702 calculateThumbLocation();
703
704 calculateTickRect();
705 calculateLabelRect();
706 }
707 }
708
709 /**
710 * Performs the opposite of installUI. Any properties or resources that need
711 * to be cleaned up will be done now. It will also uninstall any listeners
712 * it has. In addition, any properties of this UI will be nulled.
713 *
714 * @param c The {@link JComponent} that is having this UI uninstalled.
715 */
716 public void uninstallUI(JComponent c)
717 {
718 super.uninstallUI(c);
719
720 uninstallKeyboardActions(slider);
721 uninstallListeners(slider);
722
723 scrollTimer = null;
724
725 focusRect = null;
726 contentRect = null;
727 thumbRect = null;
728 trackRect = null;
729 tickRect = null;
730 labelRect = null;
731
732 focusInsets = null;
733 }
734
735 /**
736 * Initializes any default properties that this UI has from the defaults for
737 * the Basic look and feel.
738 *
739 * @param slider The {@link JSlider} that is having this UI installed.
740 */
741 protected void installDefaults(JSlider slider)
742 {
743 LookAndFeel.installColors(slider, "Slider.background",
744 "Slider.foreground");
745 LookAndFeel.installBorder(slider, "Slider.border");
746 shadowColor = UIManager.getColor("Slider.shadow");
747 highlightColor = UIManager.getColor("Slider.highlight");
748 focusColor = UIManager.getColor("Slider.focus");
749 focusInsets = UIManager.getInsets("Slider.focusInsets");
750 slider.setOpaque(true);
751 }
752
753 /**
754 * Creates a new {@link TrackListener}.
755 *
756 * @param slider The {@link JSlider} that this {@link TrackListener} is
757 * created for.
758 *
759 * @return A new {@link TrackListener}.
760 */
761 protected TrackListener createTrackListener(JSlider slider)
762 {
763 return new TrackListener();
764 }
765
766 /**
767 * Creates a new {@link ChangeListener}.
768 *
769 * @param slider The {@link JSlider} that this {@link ChangeListener} is
770 * created for.
771 *
772 * @return A new {@link ChangeListener}.
773 */
774 protected ChangeListener createChangeListener(JSlider slider)
775 {
776 return new ChangeHandler();
777 }
778
779 /**
780 * Creates a new {@link ComponentListener}.
781 *
782 * @param slider The {@link JSlider} that this {@link ComponentListener} is
783 * created for.
784 *
785 * @return A new {@link ComponentListener}.
786 */
787 protected ComponentListener createComponentListener(JSlider slider)
788 {
789 return new ComponentHandler();
790 }
791
792 /**
793 * Creates a new {@link FocusListener}.
794 *
795 * @param slider The {@link JSlider} that this {@link FocusListener} is
796 * created for.
797 *
798 * @return A new {@link FocusListener}.
799 */
800 protected FocusListener createFocusListener(JSlider slider)
801 {
802 return new FocusHandler();
803 }
804
805 /**
806 * Creates a new {@link ScrollListener}.
807 *
808 * @param slider The {@link JSlider} that this {@link ScrollListener} is
809 * created for.
810 *
811 * @return A new {@link ScrollListener}.
812 */
813 protected ScrollListener createScrollListener(JSlider slider)
814 {
815 return new ScrollListener();
816 }
817
818 /**
819 * Creates a new {@link PropertyChangeListener}.
820 *
821 * @param slider The {@link JSlider} that this {@link
822 * PropertyChangeListener} is created for.
823 *
824 * @return A new {@link PropertyChangeListener}.
825 */
826 protected PropertyChangeListener createPropertyChangeListener(JSlider slider)
827 {
828 return new PropertyChangeHandler();
829 }
830
831 /**
832 * Creates and registers all the listeners for this UI delegate. This
833 * includes creating the ScrollListener and registering it to the timer.
834 *
835 * @param slider The {@link JSlider} is having listeners installed.
836 */
837 protected void installListeners(JSlider slider)
838 {
839 propertyChangeListener = createPropertyChangeListener(slider);
840 componentListener = createComponentListener(slider);
841 trackListener = createTrackListener(slider);
842 focusListener = createFocusListener(slider);
843 changeListener = createChangeListener(slider);
844 scrollListener = createScrollListener(slider);
845
846 slider.addPropertyChangeListener(propertyChangeListener);
847 slider.addComponentListener(componentListener);
848 slider.addMouseListener(trackListener);
849 slider.addMouseMotionListener(trackListener);
850 slider.addFocusListener(focusListener);
851 slider.getModel().addChangeListener(changeListener);
852
853 scrollTimer.addActionListener(scrollListener);
854 }
855
856 /**
857 * Unregisters all the listeners that this UI delegate was using. In
858 * addition, it will also null any listeners that it was using.
859 *
860 * @param slider The {@link JSlider} that is having listeners removed.
861 */
862 protected void uninstallListeners(JSlider slider)
863 {
864 slider.removePropertyChangeListener(propertyChangeListener);
865 slider.removeComponentListener(componentListener);
866 slider.removeMouseListener(trackListener);
867 slider.removeMouseMotionListener(trackListener);
868 slider.removeFocusListener(focusListener);
869 slider.getModel().removeChangeListener(changeListener);
870
871 scrollTimer.removeActionListener(scrollListener);
872
873 propertyChangeListener = null;
874 componentListener = null;
875 trackListener = null;
876 focusListener = null;
877 changeListener = null;
878 scrollListener = null;
879 }
880
881 /**
882 * Installs any keyboard actions. The list of keys that need to be bound are
883 * listed in Basic look and feel's defaults.
884 *
885 * @param slider The {@link JSlider} that is having keyboard actions
886 * installed.
887 */
888 protected void installKeyboardActions(JSlider slider)
889 {
890 InputMap keyMap = getInputMap(JComponent.WHEN_FOCUSED);
891 SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, keyMap);
892 ActionMap map = getActionMap();
893 SwingUtilities.replaceUIActionMap(slider, map);
894 }
895
896 /**
897 * Uninstalls any keyboard actions. The list of keys used are listed in
898 * Basic look and feel's defaults.
899 *
900 * @param slider The {@link JSlider} that is having keyboard actions
901 * uninstalled.
902 */
903 protected void uninstallKeyboardActions(JSlider slider)
904 {
905 SwingUtilities.replaceUIActionMap(slider, null);
906 SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, null);
907 }
908
909 /* XXX: This is all after experimentation with SUN's implementation.
910
911 PreferredHorizontalSize seems to be 200x21.
912 PreferredVerticalSize seems to be 21x200.
913
914 MinimumHorizontalSize seems to be 36x21.
915 MinimumVerticalSize seems to be 21x36.
916
917 PreferredSize seems to be 200x63. Or Components.getBounds?
918
919 MinimumSize seems to be 36x63.
920
921 MaximumSize seems to be 32767x63.
922 */
923
924 /**
925 * This method returns the preferred size when the slider is horizontally
926 * oriented.
927 *
928 * @return The dimensions of the preferred horizontal size.
929 */
930 public Dimension getPreferredHorizontalSize()
931 {
932 Dimension dim = UIManager.getDimension("Slider.horizontalSize");
933 if (dim == null) // Just to be sure we mirror the default.
934 dim = new Dimension(200, 21);
935 return dim;
936 }
937
938 /**
939 * This method returns the preferred size when the slider is vertically
940 * oriented.
941 *
942 * @return The dimensions of the preferred vertical size.
943 */
944 public Dimension getPreferredVerticalSize()
945 {
946 Dimension dim = UIManager.getDimension("Slider.verticalSize");
947 if (dim == null) // Just to be sure we mirror the default.
948 dim = new Dimension(21, 200);
949 return dim;
950 }
951
952 /**
953 * This method returns the minimum size when the slider is horizontally
954 * oriented.
955 *
956 * @return The dimensions of the minimum horizontal size.
957 */
958 public Dimension getMinimumHorizontalSize()
959 {
960 Dimension dim = UIManager.getDimension("Slider.minimumHorizontalSize");
961 if (dim == null) // Just to be sure we mirror the default.
962 dim = new Dimension(36, 21);
963 return dim;
964 }
965
966 /**
967 * This method returns the minimum size of the slider when it is vertically
968 * oriented.
969 *
970 * @return The dimensions of the minimum vertical size.
971 */
972 public Dimension getMinimumVerticalSize()
973 {
974 Dimension dim = UIManager.getDimension("Slider.minimumVerticalSize");
975 if (dim == null) // Just to be sure we mirror the default.
976 dim = new Dimension(21, 36);
977 return dim;
978 }
979
980 /**
981 * This method returns the preferred size of the component. If it returns
982 * null, then it is up to the Layout Manager to give the {@link JComponent}
983 * a size.
984 *
985 * @param c The {@link JComponent} to find the preferred size for.
986 *
987 * @return The dimensions of the preferred size.
988 */
989 public Dimension getPreferredSize(JComponent c)
990 {
991 recalculateIfInsetsChanged();
992 Dimension dim;
993 if (slider.getOrientation() == JSlider.HORIZONTAL)
994 {
995 // Create copy here to protect the UIManager value.
996 dim = new Dimension(getPreferredHorizontalSize());
997 dim.height = insetCache.top + insetCache.bottom;
998 dim.height += focusInsets.top + focusInsets.bottom;
999 dim.height += trackRect.height + tickRect.height + labelRect.height;
1000 }
1001 else
1002 {
1003 // Create copy here to protect the UIManager value.
1004 dim = new Dimension(getPreferredVerticalSize());
1005 dim.width = insetCache.left + insetCache.right;
1006 dim.width += focusInsets.left + focusInsets.right;
1007 dim.width += trackRect.width + tickRect.width + labelRect.width;
1008 }
1009 return dim;
1010 }
1011
1012 /**
1013 * This method returns the minimum size for this {@link JSlider} for this
1014 * look and feel. If it returns null, then it is up to the Layout Manager
1015 * to give the {@link JComponent} a size.
1016 *
1017 * @param c The {@link JComponent} to find the minimum size for.
1018 *
1019 * @return The dimensions of the minimum size.
1020 */
1021 public Dimension getMinimumSize(JComponent c)
1022 {
1023 recalculateIfInsetsChanged();
1024 Dimension dim;
1025 if (slider.getOrientation() == JSlider.HORIZONTAL)
1026 {
1027 // Create copy here to protect the UIManager value.
1028 dim = new Dimension(getMinimumHorizontalSize());
1029 dim.height = insetCache.top + insetCache.bottom;
1030 dim.height += focusInsets.top + focusInsets.bottom;
1031 dim.height += trackRect.height + tickRect.height + labelRect.height;
1032 }
1033 else
1034 {
1035 // Create copy here to protect the UIManager value.
1036 dim = new Dimension(getMinimumVerticalSize());
1037 dim.width = insetCache.left + insetCache.right;
1038 dim.width += focusInsets.left + focusInsets.right;
1039 dim.width += trackRect.width + tickRect.width + labelRect.width;
1040 }
1041 return dim;
1042 }
1043
1044 /**
1045 * This method returns the maximum size for this {@link JSlider} for this
1046 * look and feel.
1047 *
1048 * @param c The {@link JComponent} to find a maximum size for.
1049 *
1050 * @return The dimensions of the maximum size.
1051 */
1052 public Dimension getMaximumSize(JComponent c)
1053 {
1054 Dimension dim = getPreferredSize(c);
1055 if (slider.getOrientation() == JSlider.HORIZONTAL)
1056 dim.width = Short.MAX_VALUE;
1057 else
1058 dim.height = Short.MAX_VALUE;
1059 return dim;
1060 }
1061
1062 /**
1063 * This method calculates all the sizes of the rectangles by delegating to
1064 * the helper methods calculateXXXRect.
1065 */
1066 protected void calculateGeometry()
1067 {
1068 calculateFocusRect();
1069 calculateContentRect();
1070 calculateThumbSize();
1071 calculateTrackBuffer();
1072 calculateTrackRect();
1073 calculateTickRect();
1074 calculateLabelRect();
1075 calculateThumbLocation();
1076 }
1077
1078 /**
1079 * This method calculates the size and position of the focusRect. This
1080 * method does not need to be called if the orientation changes.
1081 */
1082 protected void calculateFocusRect()
1083 {
1084 focusRect.x = insetCache.left;
1085 focusRect.y = insetCache.top;
1086 focusRect.width = slider.getWidth() - insetCache.left - insetCache.right;
1087 focusRect.height = slider.getHeight() - insetCache.top - insetCache.bottom;
1088 }
1089
1090 /**
1091 * Sets the width and height of the <code>thumbRect</code> field, using the
1092 * dimensions returned by {@link #getThumbSize()}.
1093 */
1094 protected void calculateThumbSize()
1095 {
1096 Dimension d = getThumbSize();
1097 thumbRect.width = d.width;
1098 thumbRect.height = d.height;
1099 }
1100
1101 /**
1102 * Updates the <code>contentRect</code> field to an area inside the
1103 * <code>focusRect</code>. This method does not need to be called if the
1104 * orientation changes.
1105 */
1106 protected void calculateContentRect()
1107 {
1108 contentRect.x = focusRect.x + focusInsets.left;
1109 contentRect.y = focusRect.y + focusInsets.top;
1110
1111 contentRect.width = focusRect.width - focusInsets.left - focusInsets.right;
1112 contentRect.height = focusRect.height - focusInsets.top
1113 - focusInsets.bottom;
1114 }
1115
1116 /**
1117 * Calculates the position of the thumbRect based on the current value of
1118 * the slider. It must take into account the orientation of the slider.
1119 */
1120 protected void calculateThumbLocation()
1121 {
1122 int value = slider.getValue();
1123
1124 if (slider.getOrientation() == JSlider.HORIZONTAL)
1125 {
1126 thumbRect.x = xPositionForValue(value) - thumbRect.width / 2;
1127 thumbRect.y = trackRect.y + 1;
1128 }
1129 else
1130 {
1131 thumbRect.x = trackRect.x + 1;
1132 thumbRect.y = yPositionForValue(value) - thumbRect.height / 2;
1133 }
1134 }
1135
1136 /**
1137 * Calculates the gap size between the edge of the <code>contentRect</code>
1138 * and the edge of the <code>trackRect</code>, storing the result in the
1139 * <code>trackBuffer</code> field. Sufficient space needs to be reserved
1140 * for the slider thumb and/or the labels at each end of the slider track.
1141 */
1142 protected void calculateTrackBuffer()
1143 {
1144 if (slider.getOrientation() == JSlider.HORIZONTAL)
1145 {
1146 int w = Math.max(getWidthOfLowValueLabel(), getWidthOfHighValueLabel());
1147 trackBuffer = Math.max(thumbRect.width / 2, w / 2);
1148
1149 }
1150 else
1151 {
1152 int h = Math.max(getHeightOfLowValueLabel(),
1153 getHeightOfHighValueLabel());
1154 trackBuffer = Math.max(thumbRect.height / 2, h / 2);
1155 }
1156 }
1157
1158 /**
1159 * Returns the size of the slider's thumb. The size is hard coded to
1160 * <code>11 x 20</code> for horizontal sliders, and <code>20 x 11</code> for
1161 * vertical sliders. Note that a new instance of {@link Dimension} is
1162 * returned for every call to this method (this seems wasteful, but
1163 * {@link Dimension} instances are not immutable, so this is probably
1164 * unavoidable).
1165 *
1166 * @return The size of the slider's thumb.
1167 */
1168 protected Dimension getThumbSize()
1169 {
1170 if (slider.getOrientation() == JSlider.HORIZONTAL)
1171 return new Dimension(11, 20);
1172 else
1173 return new Dimension(20, 11);
1174 }
1175
1176 /**
1177 * Calculates the size and position of the trackRect. It must take into
1178 * account the orientation of the slider.
1179 */
1180 protected void calculateTrackRect()
1181 {
1182 if (slider.getOrientation() == JSlider.HORIZONTAL)
1183 {
1184 int center = thumbRect.height;
1185 if (slider.getPaintTicks())
1186 center += getTickLength();
1187 if (slider.getPaintLabels())
1188 center += getHeightOfTallestLabel();
1189 trackRect.x = contentRect.x + trackBuffer;
1190 trackRect.y = contentRect.y + (contentRect.height - center - 1) / 2;
1191 trackRect.width = contentRect.width - 2 * trackBuffer;
1192 trackRect.height = thumbRect.height;
1193 }
1194 else
1195 {
1196 int center = thumbRect.width;
1197 if (slider.getPaintTicks())
1198 center += getTickLength();
1199 if (slider.getPaintLabels())
1200 center += getWidthOfWidestLabel();
1201 trackRect.x = contentRect.x + (contentRect.width - center - 1) / 2;
1202 trackRect.y = contentRect.y + trackBuffer;
1203 trackRect.width = thumbRect.width;
1204 trackRect.height = contentRect.height - 2 * trackBuffer;
1205 }
1206 }
1207
1208 /**
1209 * This method returns the height of the tick area box if the slider is
1210 * horizontal and the width of the tick area box is the slider is vertical.
1211 * It not necessarily how long the ticks will be. If a gap between the edge
1212 * of tick box and the actual tick is desired, then that will need to be
1213 * handled in the tick painting methods.
1214 *
1215 * @return The height (or width if the slider is vertical) of the tick
1216 * rectangle.
1217 */
1218 protected int getTickLength()
1219 {
1220 return 8;
1221 }
1222
1223 /**
1224 * This method calculates the size and position of the tickRect. It must
1225 * take into account the orientation of the slider.
1226 */
1227 protected void calculateTickRect()
1228 {
1229 if (slider.getOrientation() == JSlider.HORIZONTAL)
1230 {
1231 tickRect.x = trackRect.x;
1232 tickRect.y = trackRect.y + trackRect.height;
1233 tickRect.width = trackRect.width;
1234 tickRect.height = getTickLength();
1235
1236 // this makes our Mauve tests pass...can't explain it!
1237 if (!slider.getPaintTicks())
1238 {
1239 tickRect.y--;
1240 tickRect.height = 0;
1241 }
1242 }
1243 else
1244 {
1245 tickRect.x = trackRect.x + trackRect.width;
1246 tickRect.y = trackRect.y;
1247 tickRect.width = getTickLength();
1248 tickRect.height = trackRect.height;
1249
1250 // this makes our Mauve tests pass...can't explain it!
1251 if (!slider.getPaintTicks())
1252 {
1253 tickRect.x--;
1254 tickRect.width = 0;
1255 }
1256 }
1257 }
1258
1259 /**
1260 * Calculates the <code>labelRect</code> field, taking into account the
1261 * orientation of the slider.
1262 */
1263 protected void calculateLabelRect()
1264 {
1265 if (slider.getOrientation() == JSlider.HORIZONTAL)
1266 {
1267 if (slider.getPaintLabels())
1268 {
1269 labelRect.x = tickRect.x - trackBuffer;
1270 labelRect.y = tickRect.y + tickRect.height;
1271 labelRect.width = tickRect.width + trackBuffer * 2;
1272 labelRect.height = getHeightOfTallestLabel();
1273 }
1274 else
1275 {
1276 labelRect.x = tickRect.x;
1277 labelRect.y = tickRect.y + tickRect.height;
1278 labelRect.width = tickRect.width;
1279 labelRect.height = 0;
1280 }
1281 }
1282 else
1283 {
1284 if (slider.getPaintLabels())
1285 {
1286 labelRect.x = tickRect.x + tickRect.width;
1287 labelRect.y = tickRect.y - trackBuffer;
1288 labelRect.width = getWidthOfWidestLabel();
1289 labelRect.height = tickRect.height + trackBuffer * 2;
1290 }
1291 else
1292 {
1293 labelRect.x = tickRect.x + tickRect.width;
1294 labelRect.y = tickRect.y;
1295 labelRect.width = 0;
1296 labelRect.height = tickRect.height;
1297 }
1298 }
1299 }
1300
1301 /**
1302 * This method returns the width of the widest label in the slider's label
1303 * table.
1304 *
1305 * @return The width of the widest label or 0 if no label table exists.
1306 */
1307 protected int getWidthOfWidestLabel()
1308 {
1309 int widest = 0;
1310 Dictionary table = slider.getLabelTable();
1311 if (table != null)
1312 {
1313 for (Enumeration list = slider.getLabelTable().elements();
1314 list.hasMoreElements();)
1315 {
1316 Component label = (Component) list.nextElement();
1317 widest = Math.max(label.getPreferredSize().width, widest);
1318 }
1319 }
1320 return widest;
1321 }
1322
1323 /**
1324 * This method returns the height of the tallest label in the slider's label
1325 * table.
1326 *
1327 * @return The height of the tallest label or 0 if no label table exists.
1328 */
1329 protected int getHeightOfTallestLabel()
1330 {
1331 int tallest = 0;
1332 Component label;
1333
1334 if (slider.getLabelTable() == null)
1335 return 0;
1336 Dimension pref;
1337 for (Enumeration list = slider.getLabelTable().elements();
1338 list.hasMoreElements();)
1339 {
1340 Object comp = list.nextElement();
1341 if (! (comp instanceof Component))
1342 continue;
1343 label = (Component) comp;
1344 pref = label.getPreferredSize();
1345 if (pref != null && pref.height > tallest)
1346 tallest = pref.height;
1347 }
1348 return tallest;
1349 }
1350
1351 /**
1352 * Returns the width of the label whose key has the highest value, or 0 if
1353 * there are no labels.
1354 *
1355 * @return The width of the label whose key has the highest value.
1356 *
1357 * @see #getHighestValueLabel()
1358 */
1359 protected int getWidthOfHighValueLabel()
1360 {
1361 Component highValueLabel = getHighestValueLabel();
1362 if (highValueLabel != null)
1363 return highValueLabel.getPreferredSize().width;
1364 else
1365 return 0;
1366 }
1367
1368 /**
1369 * Returns the width of the label whose key has the lowest value, or 0 if
1370 * there are no labels.
1371 *
1372 * @return The width of the label whose key has the lowest value.
1373 *
1374 * @see #getLowestValueLabel()
1375 */
1376 protected int getWidthOfLowValueLabel()
1377 {
1378 Component lowValueLabel = getLowestValueLabel();
1379 if (lowValueLabel != null)
1380 return lowValueLabel.getPreferredSize().width;
1381 else
1382 return 0;
1383 }
1384
1385 /**
1386 * Returns the height of the label whose key has the highest value, or 0 if
1387 * there are no labels.
1388 *
1389 * @return The height of the high value label or 0 if no label table exists.
1390 */
1391 protected int getHeightOfHighValueLabel()
1392 {
1393 Component highValueLabel = getHighestValueLabel();
1394 if (highValueLabel != null)
1395 return highValueLabel.getPreferredSize().height;
1396 else
1397 return 0;
1398 }
1399
1400 /**
1401 * Returns the height of the label whose key has the lowest value, or 0 if
1402 * there are no labels.
1403 *
1404 * @return The height of the low value label or 0 if no label table exists.
1405 */
1406 protected int getHeightOfLowValueLabel()
1407 {
1408 Component lowValueLabel = getLowestValueLabel();
1409 if (lowValueLabel != null)
1410 return lowValueLabel.getPreferredSize().height;
1411 else
1412 return 0;
1413 }
1414
1415 /**
1416 * Returns <code>true</code> if the slider scale is to be drawn inverted,
1417 * and <code>false</code> if not.
1418 *
1419 * @return <code>true</code> if the slider is to be drawn inverted.
1420 */
1421 protected boolean drawInverted()
1422 {
1423 return slider.getInverted();
1424 }
1425
1426 /**
1427 * This method returns the label whose key has the lowest value.
1428 *
1429 * @return The low value label or null if no label table exists.
1430 */
1431 protected Component getLowestValueLabel()
1432 {
1433 Integer key = new Integer(Integer.MAX_VALUE);
1434 Integer tmpKey;
1435 Dictionary labelTable = slider.getLabelTable();
1436
1437 if (labelTable == null)
1438 return null;
1439
1440 for (Enumeration list = labelTable.keys(); list.hasMoreElements();)
1441 {
1442 Object value = list.nextElement();
1443 if (! (value instanceof Integer))
1444 continue;
1445 tmpKey = (Integer) value;
1446 if (tmpKey.intValue() < key.intValue())
1447 key = tmpKey;
1448 }
1449 Object comp = labelTable.get(key);
1450 if (! (comp instanceof Component))
1451 return null;
1452 return (Component) comp;
1453 }
1454
1455 /**
1456 * Returns the label whose key has the highest value.
1457 *
1458 * @return The label whose key has the highest value or <code>null</code> if
1459 * no label table exists.
1460 */
1461 protected Component getHighestValueLabel()
1462 {
1463 Integer key = new Integer(Integer.MIN_VALUE);
1464 Integer tmpKey;
1465 Dictionary labelTable = slider.getLabelTable();
1466
1467 if (labelTable == null)
1468 return null;
1469
1470 for (Enumeration list = labelTable.keys(); list.hasMoreElements();)
1471 {
1472 Object value = list.nextElement();
1473 if (! (value instanceof Integer))
1474 continue;
1475 tmpKey = (Integer) value;
1476 if (tmpKey.intValue() > key.intValue())
1477 key = tmpKey;
1478 }
1479 Object comp = labelTable.get(key);
1480 if (! (comp instanceof Component))
1481 return null;
1482 return (Component) comp;
1483 }
1484
1485 /**
1486 * This method is used to paint the {@link JSlider}. It delegates all its
1487 * duties to the various paint methods like paintTicks(), paintTrack(),
1488 * paintThumb(), etc.
1489 *
1490 * @param g The {@link Graphics} object to paint with.
1491 * @param c The {@link JComponent} that is being painted.
1492 */
1493 public void paint(Graphics g, JComponent c)
1494 {
1495 recalculateIfInsetsChanged();
1496 recalculateIfOrientationChanged();
1497 if (slider.getPaintTrack() && hitClip(g, trackRect))
1498 paintTrack(g);
1499 if (slider.getPaintTicks() && hitClip(g, tickRect))
1500 paintTicks(g);
1501 if (slider.getPaintLabels() && hitClip(g, labelRect))
1502 paintLabels(g);
1503 if (slider.hasFocus() && hitClip(g, focusRect))
1504 paintFocus(g);
1505 if (hitClip(g, thumbRect))
1506 paintThumb(g);
1507 }
1508
1509 /**
1510 * This method recalculates any rectangles that need to be recalculated
1511 * after the insets of the component have changed.
1512 */
1513 protected void recalculateIfInsetsChanged()
1514 {
1515 Insets insets = slider.getInsets();
1516 if (! insets.equals(insetCache))
1517 {
1518 insetCache = insets;
1519 calculateGeometry();
1520 }
1521 }
1522
1523 /**
1524 * This method recalculates any rectangles that need to be recalculated
1525 * after the orientation of the slider changes.
1526 */
1527 protected void recalculateIfOrientationChanged()
1528 {
1529 // Examining a test program shows that either Sun calls private
1530 // methods that we don't know about, or these don't do anything.
1531 calculateThumbSize();
1532 calculateTrackBuffer();
1533 calculateTrackRect();
1534 calculateThumbLocation();
1535
1536 calculateTickRect();
1537 calculateLabelRect();
1538 }
1539
1540 /**
1541 * This method is called during a repaint if the slider has focus. It draws
1542 * an outline of the focusRect using the color returned by
1543 * getFocusColor().
1544 *
1545 * @param g The {@link Graphics} object to draw with.
1546 */
1547 public void paintFocus(Graphics g)
1548 {
1549 Color saved_color = g.getColor();
1550
1551 g.setColor(getFocusColor());
1552
1553 g.drawRect(focusRect.x, focusRect.y, focusRect.width, focusRect.height);
1554
1555 g.setColor(saved_color);
1556 }
1557
1558 /**
1559 * <p>
1560 * This method is called during a repaint if the track is to be drawn. It
1561 * draws a 3D rectangle to represent the track. The track is not the size
1562 * of the trackRect. The top and left edges of the track should be outlined
1563 * with the shadow color. The bottom and right edges should be outlined
1564 * with the highlight color.
1565 * </p>
1566 * <pre>
1567 * a---d
1568 * | |
1569 * | | a------------------------d
1570 * | | | |
1571 * | | b------------------------c
1572 * | |
1573 * | |
1574 * b---c
1575 * </pre>
1576 *
1577 * <p>
1578 * The b-a-d path needs to be drawn with the shadow color and the b-c-d path
1579 * needs to be drawn with the highlight color.
1580 * </p>
1581 *
1582 * @param g The {@link Graphics} object to draw with.
1583 */
1584 public void paintTrack(Graphics g)
1585 {
1586 Color saved_color = g.getColor();
1587 int width;
1588 int height;
1589
1590 Point a = new Point(trackRect.x, trackRect.y + 1);
1591 Point b = new Point(a);
1592 Point c = new Point(a);
1593 Point d = new Point(a);
1594
1595 if (slider.getOrientation() == JSlider.HORIZONTAL)
1596 {
1597 width = trackRect.width;
1598 height = (thumbRect.height / 4 == 0) ? 1 : thumbRect.height / 4;
1599
1600 a.translate(0, (trackRect.height / 2) - (height / 2));
1601 b.translate(0, (trackRect.height / 2) + (height / 2));
1602 c.translate(trackRect.width, (trackRect.height / 2) + (height / 2));
1603 d.translate(trackRect.width, (trackRect.height / 2) - (height / 2));
1604 }
1605 else
1606 {
1607 width = (thumbRect.width / 4 == 0) ? 1 : thumbRect.width / 4;
1608 height = trackRect.height;
1609
1610 a.translate((trackRect.width / 2) - (width / 2), 0);
1611 b.translate((trackRect.width / 2) - (width / 2), trackRect.height);
1612 c.translate((trackRect.width / 2) + (width / 2), trackRect.height);
1613 d.translate((trackRect.width / 2) + (width / 2), 0);
1614 }
1615 g.setColor(Color.GRAY);
1616 g.fillRect(a.x, a.y, width, height);
1617
1618 g.setColor(getHighlightColor());
1619 g.drawLine(b.x, b.y, c.x, c.y);
1620 g.drawLine(c.x, c.y, d.x, d.y);
1621
1622 g.setColor(getShadowColor());
1623 g.drawLine(b.x, b.y, a.x, a.y);
1624 g.drawLine(a.x, a.y, d.x, d.y);
1625
1626 g.setColor(saved_color);
1627 }
1628
1629 /**
1630 * This method is called during a repaint if the ticks are to be drawn. This
1631 * method must still verify that the majorTickSpacing and minorTickSpacing
1632 * are greater than zero before drawing the ticks.
1633 *
1634 * @param g The {@link Graphics} object to draw with.
1635 */
1636 public void paintTicks(Graphics g)
1637 {
1638 int max = slider.getMaximum();
1639 int min = slider.getMinimum();
1640 int majorSpace = slider.getMajorTickSpacing();
1641 int minorSpace = slider.getMinorTickSpacing();
1642
1643 if (majorSpace > 0)
1644 {
1645 if (slider.getOrientation() == JSlider.HORIZONTAL)
1646 {
1647 g.translate(0, tickRect.y);
1648 for (int i = min; i <= max; i += majorSpace)
1649 paintMajorTickForHorizSlider(g, tickRect, xPositionForValue(i));
1650 g.translate(0, -tickRect.y);
1651 }
1652 else // JSlider.VERTICAL
1653 {
1654 g.translate(tickRect.x, 0);
1655 for (int i = min; i <= max; i += majorSpace)
1656 paintMajorTickForVertSlider(g, tickRect, yPositionForValue(i));
1657 g.translate(-tickRect.x, 0);
1658 }
1659 }
1660 if (minorSpace > 0)
1661 {
1662 if (slider.getOrientation() == JSlider.HORIZONTAL)
1663 {
1664 g.translate(0, tickRect.y);
1665 for (int i = min; i <= max; i += minorSpace)
1666 paintMinorTickForHorizSlider(g, tickRect, xPositionForValue(i));
1667 g.translate(0, -tickRect.y);
1668 }
1669 else
1670 {
1671 g.translate(tickRect.x, 0);
1672 for (int i = min; i <= max; i += minorSpace)
1673 paintMinorTickForVertSlider(g, tickRect, yPositionForValue(i));
1674 g.translate(-tickRect.x, 0);
1675 }
1676 }
1677 }
1678
1679 /* Minor ticks start at 1/4 of the height (or width) of the tickRect and
1680 extend to 1/2 of the tickRect.
1681
1682 Major ticks start at 1/4 of the height and extend to 3/4.
1683 */
1684
1685 /**
1686 * This method paints a minor tick for a horizontal slider at the given x
1687 * value. x represents the x coordinate to paint at.
1688 *
1689 * @param g The {@link Graphics} object to draw with.
1690 * @param tickBounds The tickRect rectangle.
1691 * @param x The x coordinate to draw the tick at.
1692 */
1693 protected void paintMinorTickForHorizSlider(Graphics g,
1694 Rectangle tickBounds, int x)
1695 {
1696 int y = tickRect.height / 4;
1697 Color saved = g.getColor();
1698 g.setColor(Color.BLACK);
1699
1700 g.drawLine(x, y, x, y + tickRect.height / 4);
1701 g.setColor(saved);
1702 }
1703
1704 /**
1705 * This method paints a major tick for a horizontal slider at the given x
1706 * value. x represents the x coordinate to paint at.
1707 *
1708 * @param g The {@link Graphics} object to draw with.
1709 * @param tickBounds The tickRect rectangle.
1710 * @param x The x coordinate to draw the tick at.
1711 */
1712 protected void paintMajorTickForHorizSlider(Graphics g,
1713 Rectangle tickBounds, int x)
1714 {
1715 int y = tickRect.height / 4;
1716 Color saved = g.getColor();
1717 g.setColor(Color.BLACK);
1718
1719 g.drawLine(x, y, x, y + tickRect.height / 2);
1720 g.setColor(saved);
1721 }
1722
1723 /**
1724 * This method paints a minor tick for a vertical slider at the given y
1725 * value. y represents the y coordinate to paint at.
1726 *
1727 * @param g The {@link Graphics} object to draw with.
1728 * @param tickBounds The tickRect rectangle.
1729 * @param y The y coordinate to draw the tick at.
1730 */
1731 protected void paintMinorTickForVertSlider(Graphics g, Rectangle tickBounds,
1732 int y)
1733 {
1734 int x = tickRect.width / 4;
1735 Color saved = g.getColor();
1736 g.setColor(Color.BLACK);
1737
1738 g.drawLine(x, y, x + tickRect.width / 4, y);
1739 g.setColor(saved);
1740 }
1741
1742 /**
1743 * This method paints a major tick for a vertical slider at the given y
1744 * value. y represents the y coordinate to paint at.
1745 *
1746 * @param g The {@link Graphics} object to draw with.
1747 * @param tickBounds The tickRect rectangle.
1748 * @param y The y coordinate to draw the tick at.
1749 */
1750 protected void paintMajorTickForVertSlider(Graphics g, Rectangle tickBounds,
1751 int y)
1752 {
1753 int x = tickRect.width / 4;
1754 Color saved = g.getColor();
1755 g.setColor(Color.BLACK);
1756
1757 g.drawLine(x, y, x + tickRect.width / 2, y);
1758 g.setColor(saved);
1759 }
1760
1761 /**
1762 * This method paints all the labels from the slider's label table. This
1763 * method must make sure that the label table is not null before painting
1764 * the labels. Each entry in the label table is a (integer, component)
1765 * pair. Every label is painted at the value of the integer.
1766 *
1767 * @param g The {@link Graphics} object to draw with.
1768 */
1769 public void paintLabels(Graphics g)
1770 {
1771 Dictionary table = slider.getLabelTable();
1772 if (table != null)
1773 {
1774 int min = slider.getMinimum();
1775 int max = slider.getMaximum();
1776 for (Enumeration list = table.keys(); list.hasMoreElements();)
1777 {
1778 Integer key = (Integer) list.nextElement();
1779 int value = key.intValue();
1780 if (value >= min && value <= max)
1781 {
1782 Component label = (Component) table.get(key);
1783 if (slider.getOrientation() == JSlider.HORIZONTAL)
1784 {
1785 g.translate(0, labelRect.y);
1786 paintHorizontalLabel(g, value, label);
1787 g.translate(0, -labelRect.y);
1788 }
1789 else
1790 {
1791 g.translate(labelRect.x, 0);
1792 paintVerticalLabel(g, value, label);
1793 g.translate(-labelRect.x, 0);
1794 }
1795 }
1796 }
1797 }
1798 }
1799
1800 /**
1801 * This method paints the label on the horizontal slider at the value
1802 * specified. The value is not a coordinate. It is a value within the range
1803 * of the slider. If the value is not within the range of the slider, this
1804 * method will do nothing. This method should not paint outside the
1805 * boundaries of the labelRect.
1806 *
1807 * @param g The {@link Graphics} object to draw with.
1808 * @param value The value to paint at.
1809 * @param label The label to paint.
1810 */
1811 protected void paintHorizontalLabel(Graphics g, int value, Component label)
1812 {
1813 int center = xPositionForValue(value);
1814 int left = center - label.getPreferredSize().width / 2;
1815 g.translate(left, 0);
1816 label.paint(g);
1817 g.translate(-left, 0);
1818 }
1819
1820 /**
1821 * This method paints the label on the vertical slider at the value
1822 * specified. The value is not a coordinate. It is a value within the range
1823 * of the slider. If the value is not within the range of the slider, this
1824 * method will do nothing. This method should not paint outside the
1825 * boundaries of the labelRect.
1826 *
1827 * @param g The {@link Graphics} object to draw with.
1828 * @param value The value to paint at.
1829 * @param label The label to paint.
1830 */
1831 protected void paintVerticalLabel(Graphics g, int value, Component label)
1832 {
1833 int center = yPositionForValue(value);
1834 int top = center - label.getPreferredSize().height / 2;
1835 g.translate(0, top);
1836 label.paint(g);
1837 g.translate(0, -top);
1838 }
1839
1840 /**
1841 * <p>
1842 * This method paints a thumb. There are two types of thumb:
1843 * </p>
1844 * <pre>
1845 * Vertical Horizontal
1846 * a---b a-----b
1847 * | | | \
1848 * e c | c
1849 * \ / | /
1850 * d e-----d
1851 * </pre>
1852 *
1853 * <p>
1854 * In the case of vertical thumbs, we highlight the path b-a-e-d and shadow
1855 * the path b-c-d. In the case of horizontal thumbs, we highlight the path
1856 * c-b-a-e and shadow the path c-d-e. In both cases we fill the path
1857 * a-b-c-d-e before shadows and highlights are drawn.
1858 * </p>
1859 *
1860 * @param g The graphics object to paint with
1861 */
1862 public void paintThumb(Graphics g)
1863 {
1864 Color saved_color = g.getColor();
1865
1866 Point a = new Point(thumbRect.x, thumbRect.y);
1867 Point b = new Point(a);
1868 Point c = new Point(a);
1869 Point d = new Point(a);
1870 Point e = new Point(a);
1871
1872 Polygon bright;
1873 Polygon light; // light shadow
1874 Polygon dark; // dark shadow
1875 Polygon all;
1876
1877 // This will be in X-dimension if the slider is inverted and y if it isn't.
1878 int turnPoint;
1879
1880 if (slider.getOrientation() == JSlider.HORIZONTAL)
1881 {
1882 turnPoint = thumbRect.height * 3 / 4;
1883
1884 b.translate(thumbRect.width - 1, 0);
1885 c.translate(thumbRect.width - 1, turnPoint);
1886 d.translate(thumbRect.width / 2 - 1, thumbRect.height - 1);
1887 e.translate(0, turnPoint);
1888
1889 bright = new Polygon(new int[] { b.x - 1, a.x, e.x, d.x },
1890 new int[] { b.y, a.y, e.y, d.y }, 4);
1891
1892 dark = new Polygon(new int[] { b.x, c.x, d.x + 1 }, new int[] { b.y,
1893 c.y - 1,
1894 d.y }, 3);
1895
1896 light = new Polygon(new int[] { b.x - 1, c.x - 1, d.x + 1 },
1897 new int[] { b.y + 1, c.y - 1, d.y - 1 }, 3);
1898
1899 all = new Polygon(
1900 new int[] { a.x + 1, b.x - 2, c.x - 2, d.x, e.x + 1 },
1901 new int[] { a.y + 1, b.y + 1, c.y - 1, d.y - 1, e.y },
1902 5);
1903 }
1904 else
1905 {
1906 turnPoint = thumbRect.width * 3 / 4 - 1;
1907
1908 b.translate(turnPoint, 0);
1909 c.translate(thumbRect.width - 1, thumbRect.height / 2);
1910 d.translate(turnPoint, thumbRect.height - 1);
1911 e.translate(0, thumbRect.height - 1);
1912
1913 bright = new Polygon(new int[] { c.x - 1, b.x, a.x, e.x },
1914 new int[] { c.y - 1, b.y, a.y, e.y - 1 }, 4);
1915
1916 dark = new Polygon(new int[] { c.x, d.x, e.x }, new int[] { c.y, d.y,
1917 e.y }, 3);
1918
1919 light = new Polygon(new int[] { c.x - 1, d.x, e.x + 1 },
1920 new int[] { c.y, d.y - 1, e.y - 1 }, 3);
1921 all = new Polygon(new int[] { a.x + 1, b.x, c.x - 2, c.x - 2, d.x,
1922 e.x + 1 }, new int[] { a.y + 1, b.y + 1,
1923 c.y - 1, c.y,
1924 d.y - 2, e.y - 2 },
1925 6);
1926 }
1927
1928 g.setColor(Color.WHITE);
1929 g.drawPolyline(bright.xpoints, bright.ypoints, bright.npoints);
1930
1931 g.setColor(Color.BLACK);
1932 g.drawPolyline(dark.xpoints, dark.ypoints, dark.npoints);
1933
1934 g.setColor(Color.GRAY);
1935 g.drawPolyline(light.xpoints, light.ypoints, light.npoints);
1936
1937 g.setColor(Color.LIGHT_GRAY);
1938 g.drawPolyline(all.xpoints, all.ypoints, all.npoints);
1939 g.fillPolygon(all);
1940
1941 g.setColor(saved_color);
1942 }
1943
1944 /**
1945 * This method sets the position of the thumbRect.
1946 *
1947 * @param x The new x position.
1948 * @param y The new y position.
1949 */
1950 public void setThumbLocation(int x, int y)
1951 {
1952 Rectangle union = new Rectangle(thumbRect);
1953 thumbRect.setLocation(x, y);
1954 SwingUtilities.computeUnion(thumbRect.x, thumbRect.y, thumbRect.width,
1955 thumbRect.height, union);
1956 slider.repaint(union);
1957 }
1958
1959 /**
1960 * Moves the thumb one block in the direction specified (a block is 1/10th
1961 * of the slider range). If the slider snaps to ticks, this method is
1962 * responsible for snapping it to a tick after the thumb has been moved.
1963 *
1964 * @param direction the direction (positive values increment the thumb
1965 * position by one block, zero/negative values decrement the thumb position
1966 * by one block).
1967 */
1968 public void scrollByBlock(int direction)
1969 {
1970 int unit = (slider.getMaximum() - slider.getMinimum()) / 10;
1971 int moveTo = slider.getValue();
1972 if (direction > 0)
1973 moveTo += unit;
1974 else
1975 moveTo -= unit;
1976
1977 if (slider.getSnapToTicks())
1978 moveTo = findClosestTick(moveTo);
1979
1980 slider.setValue(moveTo);
1981 }
1982
1983 /**
1984 * Moves the thumb one unit in the specified direction. If the slider snaps
1985 * to ticks, this method is responsible for snapping it to a tick after the
1986 * thumb has been moved.
1987 *
1988 * @param direction the direction (positive values increment the thumb
1989 * position by one, zero/negative values decrement the thumb position by
1990 * one).
1991 */
1992 public void scrollByUnit(int direction)
1993 {
1994 int moveTo = slider.getValue();
1995 if (direction > 0)
1996 moveTo++;
1997 else
1998 moveTo--;
1999
2000 if (slider.getSnapToTicks())
2001 moveTo = findClosestTick(moveTo);
2002
2003 slider.setValue(moveTo);
2004 }
2005
2006 /**
2007 * This method is called when there has been a click in the track and the
2008 * thumb needs to be scrolled on regular intervals. This method is only
2009 * responsible for starting the timer and not for stopping it.
2010 *
2011 * @param dir The direction to move in.
2012 */
2013 protected void scrollDueToClickInTrack(int dir)
2014 {
2015 scrollTimer.stop();
2016
2017 scrollListener.setDirection(dir);
2018 scrollListener.setScrollByBlock(true);
2019
2020 scrollTimer.start();
2021 }
2022
2023 /**
2024 * Returns the x-coordinate (relative to the component) for the given slider
2025 * value. This method assumes that the <code>trackRect</code> field is
2026 * set up.
2027 *
2028 * @param value the slider value.
2029 *
2030 * @return The x-coordinate.
2031 */
2032 protected int xPositionForValue(int value)
2033 {
2034 int min = slider.getMinimum();
2035 int max = slider.getMaximum();
2036 int len = trackRect.width;
2037 double range = max - min;
2038 double pixPerVal = len / range;
2039 int left = trackRect.x;
2040 int right = left + trackRect.width - 1;
2041 int xpos;
2042 if (! drawInverted())
2043 xpos = left + (int) Math.round(pixPerVal * ((double) value - min));
2044 else
2045 xpos = right - (int) Math.round(pixPerVal * ((double) value - min));
2046 xpos = Math.max(left, xpos);
2047 xpos = Math.min(right, xpos);
2048 return xpos;
2049 }
2050
2051 /**
2052 * Returns the y-coordinate (relative to the component) for the given slider
2053 * value. This method assumes that the <code>trackRect</code> field is
2054 * set up.
2055 *
2056 * @param value the slider value.
2057 *
2058 * @return The y-coordinate.
2059 */
2060 protected int yPositionForValue(int value)
2061 {
2062 int min = slider.getMinimum();
2063 int max = slider.getMaximum();
2064 int len = trackRect.height;
2065 double range = max - min;
2066 double pixPerVal = len / range;
2067 int top = trackRect.y;
2068 int bottom = top + trackRect.height - 1;
2069 int ypos;
2070 if (! drawInverted())
2071 ypos = top + (int) Math.round(pixPerVal * ((double) max - value));
2072 else
2073 ypos = top + (int) Math.round(pixPerVal * ((double) value - min));
2074 ypos = Math.max(top, ypos);
2075 ypos = Math.min(bottom, ypos);
2076 return ypos;
2077 }
2078
2079 /**
2080 * This method returns the value in the slider's range given the y
2081 * coordinate. If the value is out of range, it will return the closest
2082 * legal value.
2083 *
2084 * @param yPos The y coordinate to calculate a value for.
2085 *
2086 * @return The value for the y coordinate.
2087 */
2088 public int valueForYPosition(int yPos)
2089 {
2090 int min = slider.getMinimum();
2091 int max = slider.getMaximum();
2092 int len = trackRect.height;
2093
2094 int value;
2095
2096 // If the length is 0, you shouldn't be able to even see where the slider
2097 // is. This really shouldn't ever happen, but just in case, we'll return
2098 // the middle.
2099 if (len == 0)
2100 return (max - min) / 2;
2101
2102 if (! drawInverted())
2103 value = (len - (yPos - trackRect.y)) * (max - min) / len + min;
2104 else
2105 value = (yPos - trackRect.y) * (max - min) / len + min;
2106
2107 // If this isn't a legal value, then we'll have to move to one now.
2108 if (value > max)
2109 value = max;
2110 else if (value < min)
2111 value = min;
2112 return value;
2113 }
2114
2115 /**
2116 * This method returns the value in the slider's range given the x
2117 * coordinate. If the value is out of range, it will return the closest
2118 * legal value.
2119 *
2120 * @param xPos The x coordinate to calculate a value for.
2121 *
2122 * @return The value for the x coordinate.
2123 */
2124 public int valueForXPosition(int xPos)
2125 {
2126 int min = slider.getMinimum();
2127 int max = slider.getMaximum();
2128 int len = trackRect.width;
2129
2130 int value;
2131
2132 // If the length is 0, you shouldn't be able to even see where the slider
2133 // is. This really shouldn't ever happen, but just in case, we'll return
2134 // the middle.
2135 if (len == 0)
2136 return (max - min) / 2;
2137
2138 if (! drawInverted())
2139 value = (xPos - trackRect.x) * (max - min) / len + min;
2140 else
2141 value = (len - (xPos - trackRect.x)) * (max - min) / len + min;
2142
2143 // If this isn't a legal value, then we'll have to move to one now.
2144 if (value > max)
2145 value = max;
2146 else if (value < min)
2147 value = min;
2148 return value;
2149 }
2150
2151 /**
2152 * This method finds the closest value that has a tick associated with it.
2153 * This is package-private to avoid an accessor method.
2154 *
2155 * @param value The value to search from.
2156 *
2157 * @return The closest value that has a tick associated with it.
2158 */
2159 int findClosestTick(int value)
2160 {
2161 int min = slider.getMinimum();
2162 int max = slider.getMaximum();
2163 int majorSpace = slider.getMajorTickSpacing();
2164 int minorSpace = slider.getMinorTickSpacing();
2165
2166 // The default value to return is value + minor or
2167 // value + major.
2168 // Initializing at min - value leaves us with a default
2169 // return value of min, which always has tick marks
2170 // (if ticks are painted).
2171 int minor = min - value;
2172 int major = min - value;
2173
2174 // If there are no major tick marks or minor tick marks
2175 // e.g. snap is set to true but no ticks are set, then
2176 // we can just return the value.
2177 if (majorSpace <= 0 && minorSpace <= 0)
2178 return value;
2179
2180 // First check the major ticks.
2181 if (majorSpace > 0)
2182 {
2183 int lowerBound = (value - min) / majorSpace;
2184 int majLower = majorSpace * lowerBound + min;
2185 int majHigher = majorSpace * (lowerBound + 1) + min;
2186
2187 if (majHigher <= max && majHigher - value <= value - majLower)
2188 major = majHigher - value;
2189 else
2190 major = majLower - value;
2191 }
2192
2193 if (minorSpace > 0)
2194 {
2195 int lowerBound = value / minorSpace;
2196 int minLower = minorSpace * lowerBound;
2197 int minHigher = minorSpace * (lowerBound + 1);
2198
2199 if (minHigher <= max && minHigher - value <= value - minLower)
2200 minor = minHigher - value;
2201 else
2202 minor = minLower - value;
2203 }
2204
2205 // Give preference to minor ticks
2206 if (Math.abs(minor) > Math.abs(major))
2207 return value + major;
2208 else
2209 return value + minor;
2210 }
2211
2212 InputMap getInputMap(int condition)
2213 {
2214 if (condition == JComponent.WHEN_FOCUSED)
2215 return (InputMap) UIManager.get("Slider.focusInputMap");
2216 return null;
2217 }
2218
2219 /**
2220 * Returns the action map for the {@link JSlider}. All sliders share
2221 * a single action map which is created the first time this method is
2222 * called, then stored in the UIDefaults table for subsequent access.
2223 *
2224 * @return The shared action map.
2225 */
2226 ActionMap getActionMap()
2227 {
2228 ActionMap map = (ActionMap) UIManager.get("Slider.actionMap");
2229
2230 if (map == null) // first time here
2231 {
2232 map = createActionMap();
2233 if (map != null)
2234 UIManager.put("Slider.actionMap", map);
2235 }
2236 return map;
2237 }
2238
2239 /**
2240 * Creates the action map shared by all {@link JSlider} instances.
2241 * This method is called once by {@link #getActionMap()} when it
2242 * finds no action map in the UIDefaults table...after the map is
2243 * created, it gets added to the defaults table so that subsequent
2244 * calls to {@link #getActionMap()} will return the same shared
2245 * instance.
2246 *
2247 * @return The action map.
2248 */
2249 ActionMap createActionMap()
2250 {
2251 ActionMap map = new ActionMapUIResource();
2252 map.put("positiveUnitIncrement",
2253 new AbstractAction("positiveUnitIncrement") {
2254 public void actionPerformed(ActionEvent event)
2255 {
2256 JSlider slider = (JSlider) event.getSource();
2257 BasicSliderUI ui = (BasicSliderUI) slider.getUI();
2258 if (slider.getInverted())
2259 ui.scrollByUnit(BasicSliderUI.NEGATIVE_SCROLL);
2260 else
2261 ui.scrollByUnit(BasicSliderUI.POSITIVE_SCROLL);
2262 }
2263 }
2264 );
2265 map.put("negativeUnitIncrement",
2266 new AbstractAction("negativeUnitIncrement") {
2267 public void actionPerformed(ActionEvent event)
2268 {
2269 JSlider slider = (JSlider) event.getSource();
2270 BasicSliderUI ui = (BasicSliderUI) slider.getUI();
2271 if (slider.getInverted())
2272 ui.scrollByUnit(BasicSliderUI.POSITIVE_SCROLL);
2273 else
2274 ui.scrollByUnit(BasicSliderUI.NEGATIVE_SCROLL);
2275 }
2276 }
2277 );
2278 map.put("positiveBlockIncrement",
2279 new AbstractAction("positiveBlockIncrement") {
2280 public void actionPerformed(ActionEvent event)
2281 {
2282 JSlider slider = (JSlider) event.getSource();
2283 BasicSliderUI ui = (BasicSliderUI) slider.getUI();
2284 if (slider.getInverted())
2285 ui.scrollByBlock(BasicSliderUI.NEGATIVE_SCROLL);
2286 else
2287 ui.scrollByBlock(BasicSliderUI.POSITIVE_SCROLL);
2288 }
2289 }
2290 );
2291 map.put("negativeBlockIncrement",
2292 new AbstractAction("negativeBlockIncrement") {
2293 public void actionPerformed(ActionEvent event)
2294 {
2295 JSlider slider = (JSlider) event.getSource();
2296 BasicSliderUI ui = (BasicSliderUI) slider.getUI();
2297 if (slider.getInverted())
2298 ui.scrollByBlock(BasicSliderUI.POSITIVE_SCROLL);
2299 else
2300 ui.scrollByBlock(BasicSliderUI.NEGATIVE_SCROLL);
2301 }
2302 }
2303 );
2304 map.put("minScroll",
2305 new AbstractAction("minScroll") {
2306 public void actionPerformed(ActionEvent event)
2307 {
2308 JSlider slider = (JSlider) event.getSource();
2309 if (slider.getInverted())
2310 slider.setValue(slider.getMaximum());
2311 else
2312 slider.setValue(slider.getMinimum());
2313 }
2314 }
2315 );
2316 map.put("maxScroll",
2317 new AbstractAction("maxScroll") {
2318 public void actionPerformed(ActionEvent event)
2319 {
2320 JSlider slider = (JSlider) event.getSource();
2321 if (slider.getInverted())
2322 slider.setValue(slider.getMinimum());
2323 else
2324 slider.setValue(slider.getMaximum());
2325 }
2326 }
2327 );
2328 return map;
2329 }
2330
2331 /**
2332 * Small utility method to save me from typing the hell out of myself in
2333 * paint().
2334 */
2335 private boolean hitClip(Graphics g, Rectangle r)
2336 {
2337 return g.hitClip(r.x, r.y, r.width, r.height);
2338 }
2339 }