View Javadoc
1   /*
2    * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.
8    *
9    * This code is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12   * version 2 for more details (a copy is included in the LICENSE file that
13   * accompanied this code).
14   *
15   * You should have received a copy of the GNU General Public License version
16   * 2 along with this work; if not, write to the Free Software Foundation,
17   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18   *
19   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20   * or visit www.oracle.com if you need additional information or have any
21   * questions.
22   *
23   */
24  
25  package sun.jvm.hotspot.ui;
26  
27  import java.awt.event.*;
28  import javax.swing.*;
29  import javax.swing.event.*;
30  import java.math.*;
31  import java.util.*;
32  
33  /** A JScrollBar which uses BigIntegers as the representation for the
34      minimum, maximum, unit increment, etc. Interaction with the
35      buttons and track is accurate to unit and block increments;
36      however, if the scale of the scrollbar (defined by
37      getMaximumHP().subtract(getMinimumHP())) is very large, each
38      interaction with the thumb will necessarily cause extremely large
39      motion of the value. */
40  
41  public class HighPrecisionJScrollBar extends JScrollBar {
42    private BigInteger valueHP;
43    private BigInteger visibleHP;
44    private BigInteger minimumHP;
45    private BigInteger maximumHP;
46    private BigInteger unitIncrementHP;
47    private BigInteger blockIncrementHP;
48    private BigDecimal scaleFactor;
49    private BigInteger rangeHP;
50    // The underlying scrollbar runs a range from 0..BIG_RANGE-1
51    private static final int BIG_RANGE = 10000;
52    // Do we need to scale HP values up/down to fit in 0..BIG_RANGE-1?
53    private boolean    down;
54    private java.util.List changeListeners = new ArrayList();
55    // Number of digits after decimal point to use when scaling between
56    // high and low precision
57    private static final int SCALE = 20;
58  
59  
60    // This is a hack to allow us to differentiate between clicks on the
61    // arrow and track since we can't get useful information from
62    // JScrollBars' AdjustmentListener (bug in design of BasicUI
63    // classes; FIXME: file RFE.)
64    private static final int UNIT_INCREMENT  = 1;
65    private static final int BLOCK_INCREMENT = 2;
66    private static final int MINIMUM = 0;
67    private static final int MAXIMUM = 65536;
68    private boolean updating = false;
69    private int lastValueSeen = -1;
70  
71    public HighPrecisionJScrollBar() {
72      super();
73      initialize();
74      installListener();
75    }
76  
77    public HighPrecisionJScrollBar(int orientation) {
78      super(orientation);
79      initialize();
80      installListener();
81    }
82  
83    /** value, minimum and maximum should be positive */
84    public HighPrecisionJScrollBar(int orientation, BigInteger value, BigInteger minimum, BigInteger maximum) {
85      super(orientation);
86      initialize(value, minimum, maximum);
87      installListener();
88    }
89  
90    public BigInteger getValueHP() {
91      return valueHP;
92    }
93  
94  
95    /** NOTE: the real value will always be set to be (value mod
96        unitIncrement) == 0, subtracting off the mod of the passed value
97        if necessary. */
98  
99    public void setValueHP(BigInteger value) {
100     if (value.compareTo(getMaximumHP()) > 0) {
101       value = getMaximumHP();
102     } else if (value.compareTo(getMinimumHP()) < 0) {
103       value = getMinimumHP();
104     }
105     valueHP = value.subtract(value.mod(unitIncrementHP));
106     int lpValue = toUnderlyingRange(this.valueHP);
107     if (getValueHP().add(getVisibleAmountHP()).compareTo(getMaximumHP()) >= 0 ) {
108       lpValue = BIG_RANGE - getVisibleAmount();
109     }
110     lastValueSeen = lpValue;
111     setValue(lpValue);
112     fireStateChanged();
113   }
114   public BigInteger getMinimumHP() {
115     return minimumHP;
116   }
117 
118   public void setMinimumHP(BigInteger minimum) {
119     setRange(minimum, maximumHP);
120     updateScrollBarValues();
121   }
122 
123   public BigInteger getMaximumHP() {
124     return maximumHP;
125   }
126 
127   public void setMaximumHP(BigInteger maximum) {
128     setRange(minimumHP, maximum);
129     updateScrollBarValues();
130   }
131 
132   public BigInteger getVisibleAmountHP() {
133     return visibleHP;
134   }
135 
136   public void setVisibleAmountHP(BigInteger visibleAmount) {
137     this.visibleHP = visibleAmount;
138     // int lpVisAmt = toUnderlyingRange(visibleAmount);
139     // Make certain that visibleAmount value that are full range come out looking like full range
140     int lpVisAmt;
141     if (visibleAmount.compareTo(rangeHP) < 0) {
142       lpVisAmt = scaleToUnderlying(visibleAmount);
143       if (lpVisAmt == 0) {
144         lpVisAmt = 1;
145       }
146       setVisible(true);
147     } else {
148       lpVisAmt = BIG_RANGE;
149       setVisible(false);
150     }
151     setVisibleAmount(lpVisAmt);
152   }
153 
154   public BigInteger getBlockIncrementHP() {
155     return blockIncrementHP;
156   }
157 
158   public void setBlockIncrementHP(BigInteger blockIncrement) {
159     this.blockIncrementHP = blockIncrement;
160     // NOTE we do not forward this to the underlying scrollBar because of
161     // the earlier mentioned hack.
162   }
163 
164   public BigInteger getUnitIncrementHP() {
165     return unitIncrementHP;
166   }
167 
168   public void setUnitIncrementHP(BigInteger unitIncrement) {
169     this.unitIncrementHP = unitIncrement;
170     // NOTE we do not forward this to the underlying scrollBar because of
171     // the earlier mentioned hack.
172   }
173 
174 
175   public void addChangeListener(ChangeListener l) {
176     changeListeners.add(l);
177   }
178 
179   public void removeChangeListener(ChangeListener l) {
180     changeListeners.remove(l);
181   }
182 
183   //----------------------------------------------------------------------
184   // Programmatic access to scrollbar functionality
185   // (Causes change events to be sent)
186 
187   public void scrollUpOrLeft() {
188     if (updating) return;
189     beginUpdate();
190     setValueHP(getValueHP().subtract(getUnitIncrementHP()));
191     endUpdate();
192   }
193 
194   public void scrollDownOrRight() {
195     if (updating) return;
196     beginUpdate();
197     setValueHP(getValueHP().add(getUnitIncrementHP()));
198     endUpdate();
199   }
200 
201   public void pageUpOrLeft() {
202     if (updating) return;
203     beginUpdate();
204     setValueHP(getValueHP().subtract(getBlockIncrementHP()));
205     endUpdate();
206   }
207 
208   public void pageDownOrRight() {
209     if (updating) return;
210     beginUpdate();
211     setValueHP(getValueHP().add(getBlockIncrementHP()));
212     endUpdate();
213   }
214 
215   //----------------------------------------------------------------------
216   // Internals only below this point
217   //
218 
219   private void beginUpdate() {
220     updating = true;
221   }
222 
223   private void endUpdate() {
224     updating = false;
225   }
226 
227   private void initialize(BigInteger value, BigInteger minimum, BigInteger maximum) {
228     // Initialize the underlying scrollbar to the standard range values
229     // The increments are important and are how we differentiate arrow from track events
230     setMinimum(0);
231     setMaximum(BIG_RANGE - 1);
232     setValue(0);
233     setVisibleAmount(1);
234     setUnitIncrement(UNIT_INCREMENT);
235     setBlockIncrement(BLOCK_INCREMENT);
236 
237     setUnitIncrementHP(new BigInteger(Integer.toString(getUnitIncrement())));
238     setBlockIncrementHP(new BigInteger(Integer.toString(getBlockIncrement())));
239 
240     // Must set range and value first (it sets min/max)
241     setRange(minimum, maximum);
242 
243     setVisibleAmountHP(new BigInteger(Integer.toString(getVisibleAmount())));
244     setValueHP(value);
245   }
246 
247   private void initialize() {
248     BigInteger min = new BigInteger(Integer.toString(getMinimum()));
249     BigInteger max = new BigInteger(Integer.toString(getMaximum()));
250     initialize(min, min, max);
251   }
252 
253   private void setRange(BigInteger minimum, BigInteger maximum) {
254     if (minimum.compareTo(maximum) > 0 ) {
255       throw new RuntimeException("Bad scrollbar range " + minimum + " > " + maximum);
256     }
257     minimumHP = minimum;
258     maximumHP = maximum;
259     rangeHP = maximum.subtract(minimum).add(BigInteger.ONE);
260     BigInteger range2 = new BigInteger(Integer.toString(BIG_RANGE));
261     if (rangeHP.compareTo(range2) >= 0 ) {
262       down = true;
263       scaleFactor = new BigDecimal(rangeHP, SCALE).divide(new BigDecimal(range2, SCALE), BigDecimal.ROUND_DOWN).max(new BigDecimal(BigInteger.ONE));
264     } else {
265       down = false;
266       scaleFactor = new BigDecimal(range2, SCALE).divide(new BigDecimal(rangeHP, SCALE), BigDecimal.ROUND_DOWN).max(new BigDecimal(BigInteger.ONE));
267     }
268     // FIXME: should put in original scaling algorithm (shifting by
269     // number of bits) as alternative when scale between low and high
270     // precision is very large
271   }
272 
273   // A range update is complete. Rescale our computed values and
274   // inform the underlying scrollBar as needed.
275   private void updateScrollBarValues() {
276     setValueHP(getValueHP());
277     setVisibleAmountHP(getVisibleAmountHP());
278     setBlockIncrementHP(getBlockIncrementHP());
279     setUnitIncrementHP(getUnitIncrementHP());
280   }
281 
282   private BigDecimal getScaleFactor() {
283     return scaleFactor;
284   }
285 
286 
287   // Value scaling routines
288   private BigInteger scaleToHP(int i) {
289     BigDecimal ib = new BigDecimal(Integer.toString(i));
290     if (down) return ib.multiply(getScaleFactor()).toBigInteger();
291     else return ib.divide(getScaleFactor(), BigDecimal.ROUND_DOWN).toBigInteger();
292   }
293 
294   private int scaleToUnderlying(BigInteger i) {
295     BigDecimal d = new BigDecimal(i);
296     if (down) return d.divide(getScaleFactor(), BigDecimal.ROUND_DOWN).intValue();
297     else return d.multiply(getScaleFactor()).intValue();
298   }
299 
300   // Range scaling routines
301   private BigInteger toHPRange(int i) {
302     return scaleToHP(i).add(minimumHP);
303     // return ib.shiftLeft(Math.max(2, maximumHP.bitLength() - 33));
304   }
305 
306   private int toUnderlyingRange(BigInteger i) {
307     return scaleToUnderlying(i.subtract(minimumHP));
308     // return i.shiftRight(Math.max(2, maximumHP.bitLength() - 33)).intValue();
309   }
310 
311   private void installListener() {
312     super.addAdjustmentListener(new AdjustmentListener() {
313         public void adjustmentValueChanged(AdjustmentEvent e) {
314           if (updating) {
315             return;
316           }
317           beginUpdate();
318           switch (e.getAdjustmentType()) {
319           case AdjustmentEvent.TRACK:
320             int val = e.getValue();
321             int diff = val - lastValueSeen;
322             int absDiff = Math.abs(diff);
323             //            System.err.println("diff: " + diff + " absDiff: " + absDiff);
324             if (absDiff == UNIT_INCREMENT) {
325               if (diff > 0) {
326                 //                System.err.println("case 1");
327                 setValueHP(getValueHP().add(getUnitIncrementHP()));
328               } else {
329                 //                System.err.println("case 2");
330                 setValueHP(getValueHP().subtract(getUnitIncrementHP()));
331               }
332             } else if (absDiff == BLOCK_INCREMENT) {
333               if (diff > 0) {
334                 //                System.err.println("case 3");
335                 setValueHP(getValueHP().add(getBlockIncrementHP()));
336               } else {
337                 //                System.err.println("case 4");
338                 setValueHP(getValueHP().subtract(getBlockIncrementHP()));
339               }
340             } else {
341               //              System.err.println("case 5");
342               // FIXME: seem to be getting spurious update events,
343               // with diff = 0, upon mouse down/up on the track
344               if (absDiff != 0) {
345                 // Convert low-precision value to high precision
346                 // (note we lose the low bits)
347                 BigInteger i = null;
348                 if (e.getValue() == getMinimum()) {
349                   i = getMinimumHP();
350                 } else if (e.getValue() >= getMaximum() - 1) {
351                   i = getMaximumHP();
352                 } else {
353                   i = toHPRange(e.getValue());
354                 }
355                 setValueHP(i);
356               }
357             }
358             break;
359           default:
360             // Should not reach here, but leaving it a no-op in case
361             // we later get the other events (should revisit code in
362             // that case)
363             break;
364           }
365           endUpdate();
366         }
367       });
368   }
369 
370   private void fireStateChanged() {
371     ChangeEvent e = null;
372     for (Iterator iter = changeListeners.iterator(); iter.hasNext(); ) {
373       ChangeListener l = (ChangeListener) iter.next();
374       if (e == null) {
375         e = new ChangeEvent(this);
376       }
377       l.stateChanged(e);
378     }
379   }
380 
381   public static void main(String[] args) {
382     JFrame frame = new JFrame();
383     frame.setSize(300, 300);
384     // 32-bit version
385     /*
386     HighPrecisionJScrollBar hpsb =
387       new HighPrecisionJScrollBar(
388         JScrollBar.VERTICAL,
389         new BigInteger(1, new byte[] {
390           (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00}),
391         new BigInteger(1, new byte[] {
392           (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}),
393         new BigInteger(1, new byte[] {
394           (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}));
395     hpsb.setUnitIncrementHP(new BigInteger(1, new byte[] {
396       (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01}));
397     hpsb.setBlockIncrementHP(new BigInteger(1, new byte[] {
398       (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x10}));
399     */
400 
401     // 64-bit version
402     HighPrecisionJScrollBar hpsb =
403       new HighPrecisionJScrollBar(
404         JScrollBar.VERTICAL,
405         new BigInteger(1, new byte[] {
406           (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00,
407           (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}),
408         new BigInteger(1, new byte[] {
409           (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
410           (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}),
411         new BigInteger(1, new byte[] {
412           (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
413           (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}));
414     hpsb.setUnitIncrementHP(new BigInteger(1, new byte[] {
415       (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
416       (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01}));
417     hpsb.setBlockIncrementHP(new BigInteger(1, new byte[] {
418       (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
419       (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x10}));
420     hpsb.addChangeListener(new ChangeListener() {
421         public void stateChanged(ChangeEvent e) {
422           HighPrecisionJScrollBar h = (HighPrecisionJScrollBar) e.getSource();
423           System.out.println("New value = 0x" + h.getValueHP().toString(16));
424         }
425       });
426     frame.getContentPane().add(hpsb);
427     frame.setVisible(true);
428   }
429 
430 }