1 /*
   2  * Copyright 2000-2008 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  20  * CA 95054 USA or visit www.sun.com if you need additional information or
  21  * have any 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 }