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 }