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
25package sun.jvm.hotspot.ui;
26
27import java.awt.event.*;
28import javax.swing.*;
29import javax.swing.event.*;
30import java.math.*;
31import 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
41public 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}
431