source: tmcsimulator-scriptbuilder/trunk/src/scriptbuilder/gui/drawers/RangeSliderUI.java @ 25

Revision 25, 21.1 KB checked in by jdalbey, 9 years ago (diff)

RangeSlider? added to drawers package.

Line 
1import java.awt.Color;
2import java.awt.Dimension;
3import java.awt.Graphics;
4import java.awt.Graphics2D;
5import java.awt.Rectangle;
6import java.awt.RenderingHints;
7import java.awt.Shape;
8import java.awt.event.MouseEvent;
9import java.awt.geom.Ellipse2D;
10
11import javax.swing.JComponent;
12import javax.swing.JSlider;
13import javax.swing.SwingUtilities;
14import javax.swing.event.ChangeEvent;
15import javax.swing.event.ChangeListener;
16import javax.swing.plaf.basic.BasicSliderUI;
17/*
18The MIT License
19
20Copyright (c) 2010 Ernest Yu. All rights reserved.
21
22Permission is hereby granted, free of charge, to any person obtaining a copy
23of this software and associated documentation files (the "Software"), to deal
24in the Software without restriction, including without limitation the rights
25to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
26copies of the Software, and to permit persons to whom the Software is
27furnished to do so, subject to the following conditions:
28
29The above copyright notice and this permission notice shall be included in
30all copies or substantial portions of the Software.
31
32THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
38THE SOFTWARE.
39*/
40
41/**
42 * UI delegate for the RangeSlider component.  RangeSliderUI paints two thumbs,
43 * one for the lower value and one for the upper value.
44 */
45class RangeSliderUI extends BasicSliderUI {
46
47    /** Color of selected range. */
48    private Color rangeColor = Color.GREEN;
49   
50    /** Location and size of thumb for upper value. */
51    private Rectangle upperThumbRect;
52    /** Indicator that determines whether upper thumb is selected. */
53    private boolean upperThumbSelected;
54   
55    /** Indicator that determines whether lower thumb is being dragged. */
56    private transient boolean lowerDragging;
57    /** Indicator that determines whether upper thumb is being dragged. */
58    private transient boolean upperDragging;
59   
60    /**
61     * Constructs a RangeSliderUI for the specified slider component.
62     * @param b RangeSlider
63     */
64    public RangeSliderUI(RangeSlider b) {
65        super(b);
66    }
67   
68    /**
69     * Installs this UI delegate on the specified component.
70     */
71    @Override
72    public void installUI(JComponent c) {
73        upperThumbRect = new Rectangle();
74        super.installUI(c);
75    }
76
77    /**
78     * Creates a listener to handle track events in the specified slider.
79     */
80    @Override
81    protected TrackListener createTrackListener(JSlider slider) {
82        return new RangeTrackListener();
83    }
84
85    /**
86     * Creates a listener to handle change events in the specified slider.
87     */
88    @Override
89    protected ChangeListener createChangeListener(JSlider slider) {
90        return new ChangeHandler();
91    }
92   
93    /**
94     * Updates the dimensions for both thumbs.
95     */
96    @Override
97    protected void calculateThumbSize() {
98        // Call superclass method for lower thumb size.
99        super.calculateThumbSize();
100       
101        // Set upper thumb size.
102        upperThumbRect.setSize(thumbRect.width, thumbRect.height);
103    }
104   
105    /**
106     * Updates the locations for both thumbs.
107     */
108    @Override
109    protected void calculateThumbLocation() {
110        // Call superclass method for lower thumb location.
111        super.calculateThumbLocation();
112       
113        // Adjust upper value to snap to ticks if necessary.
114        if (slider.getSnapToTicks()) {
115            int upperValue = slider.getValue() + slider.getExtent();
116            int snappedValue = upperValue; 
117            int majorTickSpacing = slider.getMajorTickSpacing();
118            int minorTickSpacing = slider.getMinorTickSpacing();
119            int tickSpacing = 0;
120           
121            if (minorTickSpacing > 0) {
122                tickSpacing = minorTickSpacing;
123            } else if (majorTickSpacing > 0) {
124                tickSpacing = majorTickSpacing;
125            }
126
127            if (tickSpacing != 0) {
128                // If it's not on a tick, change the value
129                if ((upperValue - slider.getMinimum()) % tickSpacing != 0) {
130                    float temp = (float)(upperValue - slider.getMinimum()) / (float)tickSpacing;
131                    int whichTick = Math.round(temp);
132                    snappedValue = slider.getMinimum() + (whichTick * tickSpacing);
133                }
134
135                if (snappedValue != upperValue) { 
136                    slider.setExtent(snappedValue - slider.getValue());
137                }
138            }
139        }
140       
141        // Calculate upper thumb location.  The thumb is centered over its
142        // value on the track.
143        if (slider.getOrientation() == JSlider.HORIZONTAL) {
144            int upperPosition = xPositionForValue(slider.getValue() + slider.getExtent());
145            upperThumbRect.x = upperPosition - (upperThumbRect.width / 2);
146            upperThumbRect.y = trackRect.y;
147           
148        } else {
149            int upperPosition = yPositionForValue(slider.getValue() + slider.getExtent());
150            upperThumbRect.x = trackRect.x;
151            upperThumbRect.y = upperPosition - (upperThumbRect.height / 2);
152        }
153    }
154   
155    /**
156     * Returns the size of a thumb.
157     */
158    @Override
159    protected Dimension getThumbSize() {
160        return new Dimension(12, 12);
161    }
162
163    /**
164     * Paints the slider.  The selected thumb is always painted on top of the
165     * other thumb.
166     */
167    @Override
168    public void paint(Graphics g, JComponent c) {
169        super.paint(g, c);
170       
171        Rectangle clipRect = g.getClipBounds();
172        if (upperThumbSelected) {
173            // Paint lower thumb first, then upper thumb.
174            if (clipRect.intersects(thumbRect)) {
175                paintLowerThumb(g);
176            }
177            if (clipRect.intersects(upperThumbRect)) {
178                paintUpperThumb(g);
179            }
180           
181        } else {
182            // Paint upper thumb first, then lower thumb.
183            if (clipRect.intersects(upperThumbRect)) {
184                paintUpperThumb(g);
185            }
186            if (clipRect.intersects(thumbRect)) {
187                paintLowerThumb(g);
188            }
189        }
190    }
191   
192    /**
193     * Paints the track.
194     */
195    @Override
196    public void paintTrack(Graphics g) {
197        // Draw track.
198        super.paintTrack(g);
199       
200        Rectangle trackBounds = trackRect;
201       
202        if (slider.getOrientation() == JSlider.HORIZONTAL) {
203            // Determine position of selected range by moving from the middle
204            // of one thumb to the other.
205            int lowerX = thumbRect.x + (thumbRect.width / 2);
206            int upperX = upperThumbRect.x + (upperThumbRect.width / 2);
207           
208            // Determine track position.
209            int cy = (trackBounds.height / 2) - 2;
210
211            // Save color and shift position.
212            Color oldColor = g.getColor();
213            g.translate(trackBounds.x, trackBounds.y + cy);
214           
215            // Draw selected range.
216            g.setColor(rangeColor);
217            for (int y = 0; y <= 3; y++) {
218                g.drawLine(lowerX - trackBounds.x, y, upperX - trackBounds.x, y);
219            }
220
221            // Restore position and color.
222            g.translate(-trackBounds.x, -(trackBounds.y + cy));
223            g.setColor(oldColor);
224           
225        } else {
226            // Determine position of selected range by moving from the middle
227            // of one thumb to the other.
228            int lowerY = thumbRect.x + (thumbRect.width / 2);
229            int upperY = upperThumbRect.x + (upperThumbRect.width / 2);
230           
231            // Determine track position.
232            int cx = (trackBounds.width / 2) - 2;
233
234            // Save color and shift position.
235            Color oldColor = g.getColor();
236            g.translate(trackBounds.x + cx, trackBounds.y);
237
238            // Draw selected range.
239            g.setColor(rangeColor);
240            for (int x = 0; x <= 3; x++) {
241                g.drawLine(x, lowerY - trackBounds.y, x, upperY - trackBounds.y);
242            }
243           
244            // Restore position and color.
245            g.translate(-(trackBounds.x + cx), -trackBounds.y);
246            g.setColor(oldColor);
247        }
248    }
249   
250    /**
251     * Overrides superclass method to do nothing.  Thumb painting is handled
252     * within the <code>paint()</code> method.
253     */
254    @Override
255    public void paintThumb(Graphics g) {
256        // Do nothing.
257    }
258
259    /**
260     * Paints the thumb for the lower value using the specified graphics object.
261     */
262    private void paintLowerThumb(Graphics g) {
263        Rectangle knobBounds = thumbRect;
264        int w = knobBounds.width;
265        int h = knobBounds.height;     
266       
267        // Create graphics copy.
268        Graphics2D g2d = (Graphics2D) g.create();
269
270        // Create default thumb shape.
271        Shape thumbShape = createThumbShape(w - 1, h - 1);
272
273        // Draw thumb.
274        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
275            RenderingHints.VALUE_ANTIALIAS_ON);
276        g2d.translate(knobBounds.x, knobBounds.y);
277
278        g2d.setColor(Color.CYAN);
279        g2d.fill(thumbShape);
280
281        g2d.setColor(Color.BLUE);
282        g2d.draw(thumbShape);
283       
284        // Dispose graphics.
285        g2d.dispose();
286    }
287   
288    /**
289     * Paints the thumb for the upper value using the specified graphics object.
290     */
291    private void paintUpperThumb(Graphics g) {
292        Rectangle knobBounds = upperThumbRect;
293        int w = knobBounds.width;
294        int h = knobBounds.height;     
295       
296        // Create graphics copy.
297        Graphics2D g2d = (Graphics2D) g.create();
298
299        // Create default thumb shape.
300        Shape thumbShape = createThumbShape(w - 1, h - 1);
301
302        // Draw thumb.
303        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
304            RenderingHints.VALUE_ANTIALIAS_ON);
305        g2d.translate(knobBounds.x, knobBounds.y);
306
307        g2d.setColor(Color.PINK);
308        g2d.fill(thumbShape);
309
310        g2d.setColor(Color.RED);
311        g2d.draw(thumbShape);
312
313        // Dispose graphics.
314        g2d.dispose();
315    }
316
317    /**
318     * Returns a Shape representing a thumb.
319     */
320    private Shape createThumbShape(int width, int height) {
321        // Use circular shape.
322        Ellipse2D shape = new Ellipse2D.Double(0, 0, width, height);
323        return shape;
324    }
325   
326    /**
327     * Sets the location of the upper thumb, and repaints the slider.  This is
328     * called when the upper thumb is dragged to repaint the slider.  The
329     * <code>setThumbLocation()</code> method performs the same task for the
330     * lower thumb.
331     */
332    private void setUpperThumbLocation(int x, int y) {
333        Rectangle upperUnionRect = new Rectangle();
334        upperUnionRect.setBounds(upperThumbRect);
335
336        upperThumbRect.setLocation(x, y);
337
338        SwingUtilities.computeUnion(upperThumbRect.x, upperThumbRect.y, upperThumbRect.width, upperThumbRect.height, upperUnionRect);
339        slider.repaint(upperUnionRect.x, upperUnionRect.y, upperUnionRect.width, upperUnionRect.height);
340    }
341   
342    /**
343     * Moves the selected thumb in the specified direction by a block increment.
344     * This method is called when the user presses the Page Up or Down keys.
345     */
346    public void scrollByBlock(int direction) {
347        synchronized (slider) {
348            int blockIncrement = (slider.getMaximum() - slider.getMinimum()) / 10;
349            if (blockIncrement <= 0 && slider.getMaximum() > slider.getMinimum()) {
350                blockIncrement = 1;
351            }
352            int delta = blockIncrement * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
353           
354            if (upperThumbSelected) {
355                int oldValue = ((RangeSlider) slider).getUpperValue();
356                ((RangeSlider) slider).setUpperValue(oldValue + delta);
357            } else {
358                int oldValue = slider.getValue();
359                slider.setValue(oldValue + delta);
360            }
361        }
362    }
363   
364    /**
365     * Moves the selected thumb in the specified direction by a unit increment.
366     * This method is called when the user presses one of the arrow keys.
367     */
368    public void scrollByUnit(int direction) {
369        synchronized (slider) {
370            int delta = 1 * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
371           
372            if (upperThumbSelected) {
373                int oldValue = ((RangeSlider) slider).getUpperValue();
374                ((RangeSlider) slider).setUpperValue(oldValue + delta);
375            } else {
376                int oldValue = slider.getValue();
377                slider.setValue(oldValue + delta);
378            }
379        }       
380    }
381   
382    /**
383     * Listener to handle model change events.  This calculates the thumb
384     * locations and repaints the slider if the value change is not caused by
385     * dragging a thumb.
386     */
387    public class ChangeHandler implements ChangeListener {
388        public void stateChanged(ChangeEvent arg0) {
389            if (!lowerDragging && !upperDragging) {
390                calculateThumbLocation();
391                slider.repaint();
392            }
393        }
394    }
395   
396    /**
397     * Listener to handle mouse movements in the slider track.
398     */
399    public class RangeTrackListener extends TrackListener {
400       
401        @Override
402        public void mousePressed(MouseEvent e) {
403            if (!slider.isEnabled()) {
404                return;
405            }
406
407            currentMouseX = e.getX();
408            currentMouseY = e.getY();
409
410            if (slider.isRequestFocusEnabled()) {
411                slider.requestFocus();
412            }
413           
414            // Determine which thumb is pressed.  If the upper thumb is
415            // selected (last one dragged), then check its position first;
416            // otherwise check the position of the lower thumb first.
417            boolean lowerPressed = false;
418            boolean upperPressed = false;
419            if (upperThumbSelected || slider.getMinimum() == slider.getValue()) {
420                if (upperThumbRect.contains(currentMouseX, currentMouseY)) {
421                    upperPressed = true;
422                } else if (thumbRect.contains(currentMouseX, currentMouseY)) {
423                    lowerPressed = true;
424                }
425            } else {
426                if (thumbRect.contains(currentMouseX, currentMouseY)) {
427                    lowerPressed = true;
428                } else if (upperThumbRect.contains(currentMouseX, currentMouseY)) {
429                    upperPressed = true;
430                }
431            }
432
433            // Handle lower thumb pressed.
434            if (lowerPressed) {
435                switch (slider.getOrientation()) {
436                case JSlider.VERTICAL:
437                    offset = currentMouseY - thumbRect.y;
438                    break;
439                case JSlider.HORIZONTAL:
440                    offset = currentMouseX - thumbRect.x;
441                    break;
442                }
443                upperThumbSelected = false;
444                lowerDragging = true;
445                return;
446            }
447            lowerDragging = false;
448           
449            // Handle upper thumb pressed.
450            if (upperPressed) {
451                switch (slider.getOrientation()) {
452                case JSlider.VERTICAL:
453                    offset = currentMouseY - upperThumbRect.y;
454                    break;
455                case JSlider.HORIZONTAL:
456                    offset = currentMouseX - upperThumbRect.x;
457                    break;
458                }
459                upperThumbSelected = true;
460                upperDragging = true;
461                return;
462            }
463            upperDragging = false;
464        }
465       
466        @Override
467        public void mouseReleased(MouseEvent e) {
468            lowerDragging = false;
469            upperDragging = false;
470            slider.setValueIsAdjusting(false);
471            super.mouseReleased(e);
472        }
473       
474        @Override
475        public void mouseDragged(MouseEvent e) {
476            if (!slider.isEnabled()) {
477                return;
478            }
479
480            currentMouseX = e.getX();
481            currentMouseY = e.getY();
482
483            if (lowerDragging) {
484                slider.setValueIsAdjusting(true);
485                moveLowerThumb();
486               
487            } else if (upperDragging) {
488                slider.setValueIsAdjusting(true);
489                moveUpperThumb();
490            }
491        }
492       
493        @Override
494        public boolean shouldScroll(int direction) {
495            return false;
496        }
497
498        /**
499         * Moves the location of the lower thumb, and sets its corresponding
500         * value in the slider.
501         */
502        private void moveLowerThumb() {
503            int thumbMiddle = 0;
504           
505            switch (slider.getOrientation()) {
506            case JSlider.VERTICAL:     
507                int halfThumbHeight = thumbRect.height / 2;
508                int thumbTop = currentMouseY - offset;
509                int trackTop = trackRect.y;
510                int trackBottom = trackRect.y + (trackRect.height - 1);
511                int vMax = yPositionForValue(slider.getValue() + slider.getExtent());
512
513                // Apply bounds to thumb position.
514                if (drawInverted()) {
515                    trackBottom = vMax;
516                } else {
517                    trackTop = vMax;
518                }
519                thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight);
520                thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight);
521
522                setThumbLocation(thumbRect.x, thumbTop);
523
524                // Update slider value.
525                thumbMiddle = thumbTop + halfThumbHeight;
526                slider.setValue(valueForYPosition(thumbMiddle));
527                break;
528               
529            case JSlider.HORIZONTAL:
530                int halfThumbWidth = thumbRect.width / 2;
531                int thumbLeft = currentMouseX - offset;
532                int trackLeft = trackRect.x;
533                int trackRight = trackRect.x + (trackRect.width - 1);
534                int hMax = xPositionForValue(slider.getValue() + slider.getExtent());
535
536                // Apply bounds to thumb position.
537                if (drawInverted()) {
538                    trackLeft = hMax;
539                } else {
540                    trackRight = hMax;
541                }
542                thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
543                thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
544
545                setThumbLocation(thumbLeft, thumbRect.y);
546
547                // Update slider value.
548                thumbMiddle = thumbLeft + halfThumbWidth;
549                slider.setValue(valueForXPosition(thumbMiddle));
550                break;
551               
552            default:
553                return;
554            }
555        }
556
557        /**
558         * Moves the location of the upper thumb, and sets its corresponding
559         * value in the slider.
560         */
561        private void moveUpperThumb() {
562            int thumbMiddle = 0;
563           
564            switch (slider.getOrientation()) {
565            case JSlider.VERTICAL:     
566                int halfThumbHeight = thumbRect.height / 2;
567                int thumbTop = currentMouseY - offset;
568                int trackTop = trackRect.y;
569                int trackBottom = trackRect.y + (trackRect.height - 1);
570                int vMin = yPositionForValue(slider.getValue());
571
572                // Apply bounds to thumb position.
573                if (drawInverted()) {
574                    trackTop = vMin;
575                } else {
576                    trackBottom = vMin;
577                }
578                thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight);
579                thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight);
580
581                setUpperThumbLocation(thumbRect.x, thumbTop);
582
583                // Update slider extent.
584                thumbMiddle = thumbTop + halfThumbHeight;
585                slider.setExtent(valueForYPosition(thumbMiddle) - slider.getValue());
586                break;
587               
588            case JSlider.HORIZONTAL:
589                int halfThumbWidth = thumbRect.width / 2;
590                int thumbLeft = currentMouseX - offset;
591                int trackLeft = trackRect.x;
592                int trackRight = trackRect.x + (trackRect.width - 1);
593                int hMin = xPositionForValue(slider.getValue());
594
595                // Apply bounds to thumb position.
596                if (drawInverted()) {
597                    trackRight = hMin;
598                } else {
599                    trackLeft = hMin;
600                }
601                thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
602                thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
603
604                setUpperThumbLocation(thumbLeft, thumbRect.y);
605               
606                // Update slider extent.
607                thumbMiddle = thumbLeft + halfThumbWidth;
608                slider.setExtent(valueForXPosition(thumbMiddle) - slider.getValue());
609                break;
610               
611            default:
612                return;
613            }
614        }
615    }
616}
Note: See TracBrowser for help on using the repository browser.