Warning: Can't use blame annotator:
svn blame failed on trunk/src/scriptbuilder/gui/drawers/RangeSliderUI.java: ("Can't find a temporary directory: Internal error", 20014)

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

Revision 84, 17.3 KB checked in by jdalbey, 9 years ago (diff)

RangeSlider?: Fix defect that was allowing slider thumb to be dragged too far right.

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