home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Programming Languages Suite
/
ProgLangD.iso
/
VCAFE.3.0A
/
Main.bin
/
CurrencyEngine.java
< prev
next >
Wrap
Text File
|
1998-12-09
|
14KB
|
365 lines
/*
Engine class for support of currency field input.
Created 5/4/98 by Paul Lancaster.
*/
package com.symantec.itools.swing;
import java.math.BigDecimal; // used for rounding
import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
public class CurrencyEngine {
// default constructor
public CurrencyEngine() {
this ( getDefaultDecimalSeparator() ,
getDefaultGroupingSeparator() );
}
public CurrencyEngine( char decimalPoint , char separator ) {
super();
setDecimalPoint ( decimalPoint );
setSeparator ( separator );
}
public void setCommas (boolean b) { _commas = b ; }
public void setCurrencyLeading (boolean b) { _currencySymbolLeads = b ; }
public void setATMmode (boolean b) { _ATMmode = b ; }
public void setCurrencySymbol (String s) { _currencySymbol = s ; }
public void setDecimalPoint (char c) { _decimalPoint = c ; }
public void setSeparator (char c) { _separator = c ; }
public void setDigitsAfterDecimal(int c) { _digitsAfterDecimal = c ; }
public boolean getCommas ( ) { return _commas ; }
public boolean getCurrencyLeading ( ) { return _currencySymbolLeads ; }
public boolean getATMmode ( ) { return _ATMmode ; }
public String getCurrencySymbol ( ) { return _currencySymbol ; }
public char getDecimalPoint ( ) { return _decimalPoint ; }
public char getSeparator ( ) { return _separator ; }
public int getDigitsAfterDecimal( ) { return _digitsAfterDecimal ; }
/* Called to initialize the display of the masked data.
The first parameter is the current data in the field.
The second parameter holds the string that should be displayed.
The return value is the initial caret position.
*/
public int initDisplay(String data, StringBuffer newData) {
if (data.length() == 0) { // no input data
normalize(newData);
} else { // data coming in
newData.append(data);
scale(newData);
}
return _ATMmode ? newData.length() : 0;
}
/* This is the main workhorse method.
It's called for every key stroke corresponding to displayable
characters once editing begins.
The 1st parameter is the user keystroke event object.
The 2nd parameter is the current cursor position (zero based).
The 3rd parameter is the current text from the component.
The 4th parameter is output and is what should be displayed in the component.
The 5th and 6th parameters are the selection start and end, respectively.
The return value is the new cursor position within the "newData"
parameter (zero based), unless it is negative, in which case:
-1 means the input is inconsistent.
*/
public int processKey(java.awt.event.KeyEvent e, int pos, String data,
StringBuffer newData, int selStart, int selEnd) {
char key = e.getKeyChar();
newData.append(data); // init output to input
int decpos = data.indexOf(_decimalPoint);
int datalen = data.length();
int keyCode = e.getKeyCode();
if (_ATMmode) {
switch (keyCode) {
case e.VK_LEFT:
return pos - (pos == 0 ? pos : (pos == decpos + 1 ? 2 : 1));
case e.VK_RIGHT:
return pos + (pos == datalen ? 0 : (pos == decpos - 1 ? 2 : 1));
case e.VK_BACK_SPACE:
case e.VK_DELETE:
if (pos == decpos) // don't allow decimal point deletion
return -1;
if (selStart < selEnd) {
clearSelectedText(selStart, selEnd, newData, true);
return selStart;
}
deleteChar(newData, pos == datalen ? pos - 1 : pos);
normalize(newData);
int delta = newData.length() - datalen;
return pos == decpos - 1 ? (decpos + (0 == delta ? 1 : 0)) : pos + delta;
} // end switch on navigation key
} else { // not ATM mode
// Have to handle backspace & delete in non-ATM mode to ensure
// right justification occurs if component is awt.TextField
switch (keyCode) {
case e.VK_BACK_SPACE:
if (selEnd == selStart && pos > 0) {
clearSelectedText(pos - 1, pos, newData, false);
return pos - 1;
} else {
clearSelectedText(selStart, selEnd, newData, false);
return pos;
}
case e.VK_DELETE:
if (selEnd == selStart && pos < datalen)
clearSelectedText(pos, pos + 1, newData, false);
else
clearSelectedText(selStart, selEnd, newData, false);
return pos;
} // end switch on navigation key
}
if (!Character.isDigit(key)) { // input not a digit?
if (key != _decimalPoint || _ATMmode)
return -1; // must be decimal point if not digit
if (decpos != -1)
return pos <= decpos ? decpos + 1 : -1;
}
clearSelectedText(selStart, selEnd, newData, true);
if (_ATMmode) {
//if (selStart < selEnd && decpos < selStart) {
if (selStart < selEnd ) {
//this is a quick fix to avoid StringIndexOutOfBoundsExceptions
//@todo:handle cases (properly) when a digit to the left of decimal
// separator shifts to the right when selection is deleted
newData.insert(selStart, key);
}else{
newData.insert(pos, key);
}
normalize(newData);
return selStart < selEnd ? selStart : pos + newData.length() - datalen;
} else{
//newData.insert(pos, key);
//this is a quick fix to avoid StringIndexOutOfBoundsExceptions
if (selStart < selEnd ) {
pos = selStart;
newData.insert(selStart, key);
}else{
newData.insert(pos, key);
}
}
return pos + 1;
}
// Return true iff the engine handles the given key stroke.
public boolean isHandledKey(java.awt.event.KeyEvent e) {
if (!Character.isISOControl(e.getKeyChar()))
return true; // we handle all non-control characters
switch (e.getKeyCode()) { // here are the controls we handle
case e.VK_BACK_SPACE:
case e.VK_DELETE:
return true;
case e.VK_RIGHT:
case e.VK_LEFT:
return _ATMmode && !e.isShiftDown();
}
return false; // don't handle control chars by default
}
public void postFormat(String data, StringBuffer newData) {
newData.append(data);
scale(newData);
data = newData.toString();
newData.setLength(0);
if ( _currencySymbolLeads ) {
newData.append(_currencySymbol);
}
int j = newData.length();
newData.append(data);
if (!_currencySymbolLeads) {
newData.append(_currencySymbol);
}
int decpos = data.indexOf(_decimalPoint);
// if number of digits after decimal == 0,
// scale() returns a number-string with no decimal point
// In such cases, it is virtually at the end.
if ( decpos == -1 ) {
decpos = data.length();
}
for (int i = 0; i < decpos; i++, j++)
//if (_commas && i > 0 && i % 3 == decpos % 3)
if (_commas && i > 0 && i % defDecimalFormat.getGroupingSize() == decpos % defDecimalFormat.getGroupingSize())
newData.insert(j++, _separator);
if ( debug ) {
System.out.println ( "postformat : " + newData );
}
}
public void cut(StringBuffer newData, int selStart, int selEnd) {
clearSelectedText(selStart, selEnd, newData, true);
}
public void paste(StringBuffer data, String pasteData, int pos, int selStart, int selEnd) {
clearSelectedText(selStart, selEnd, data, false);
String s = data.toString();
int decpos = s.indexOf(_decimalPoint);
data.setLength(0);
// Sanitize the paste data to remove non-digits.
StringBuffer pd = new StringBuffer();
int pastelen = pasteData.length();
for (int i = 0; i < pastelen; i++) {
char c = pasteData.charAt(i);
if (Character.isDigit(c) || (decpos == -1 && _decimalPoint == c))
pd.append(c);
}
data.append(s.substring(0, pos) + pd.toString() + s.substring(pos, s.length()));
if (_ATMmode)
normalize(data);
}
void clearSelectedText(int selStart, int selEnd, StringBuffer data, boolean norm) {
int selLen = selEnd - selStart;
if (selLen > 0) { // only if selected text exists
String s = data.toString();
int oldlen = data.length();
data.insert(0, s.substring(0, selStart) + s.substring(selEnd));
data.setLength(oldlen - selLen);
if (_ATMmode && norm)
normalize(data);
}
}
/* The input is a digit string (possibly with a decimal point) that's presumed
to be the mantissa of a floating point value whose exponent is the number of digits
minus the digitsAfterDecimal property. The incoming decimal point position is
ignored unless it's found to be correct. It's adjusted to be to the left of
exactly digitsAfterDecimal digits, with zero fill as required. Values less
than unity are given a leading zero.
Examples: (with _digitsAfterDecimal == 2)
Input Output
25 0.25
1.375 13.75
295.4 29.54
*/
private void normalize(StringBuffer sigfigs) {
int decpos = sigfigs.toString().indexOf(_decimalPoint);
int lod = sigfigs.toString().length() - 1 - _digitsAfterDecimal; // # digits left of dec. pt.
if (decpos != lod || decpos < 1) { // if decimal point's absent or in wrong place
if (decpos != -1) { // remove existing decimal point
deleteChar(sigfigs, decpos);
lod--; // since first LOD calculation assumed no dec. pt.
}
while (lod++ < 0) // zero fill to right of decimal if needed
sigfigs.insert(0, '0');
sigfigs.insert(sigfigs.length() - _digitsAfterDecimal, _decimalPoint);
}
// Trim down to at most one leadng zero
while (sigfigs.charAt(0) == '0' && sigfigs.charAt(1) != _decimalPoint)
deleteChar(sigfigs, 0);
}
/* Reformats the parameter to have the right number of digits after the
decimal point. The position (or absence) of the decimal point on entry
determines the exponent. If the input number of digits to the right
of the decimal is less than _digitsAfterDecimal, zeros are appended.
If there are more than _digitsAfterDecimal digits after the decimal,
rounding is done.
Examples: (with _digitsAfterDecimal == 2)
Input Output
25 25.00
1.375 1.38
295.4 295.40
999.995 1000.00
*/
private void scale(StringBuffer newData) {
if (newData.length() == 0) {
newData.append('0');
}
String data = newData.toString();
double dblData = 0;
//hopefully nobody would use this as their decimal separator
char tempChar = '~';
if ( _separator != getDefaultGroupingSeparator() ) {
//first replace it with some other character
data = data.replace ( _separator , tempChar );
}
if ( _decimalPoint != getDefaultDecimalSeparator() ) {
// DecimalFormat.parse needs locale specific decimal point
data = data.replace ( _decimalPoint , getDefaultDecimalSeparator() );
}
if ( _separator != getDefaultGroupingSeparator() ) {
data = data.replace ( tempChar , getDefaultGroupingSeparator() );
}
//parse using the locale specific decimal format
try{
dblData = defDecimalFormat.parse( data ).doubleValue();
}catch (Exception e ){
// in case of exceptions throw a number format exception
// which is a runtime exception as aginst parse exception
// which is a language exception
throw new NumberFormatException ( e.getMessage() );
}
data = (new BigDecimal(dblData)).setScale(_digitsAfterDecimal, BigDecimal.ROUND_HALF_UP).toString();
// data would now have '.' as decimal separator
// Restore things to what they were earlier
if (_decimalPoint != '.' ) {
data = data.replace( '.' , _decimalPoint );
}
if ( debug ) {
System.out.println ( "scale : " + data );
}
newData.setLength(0);
newData.append(data);
}
// Delete the character at the given position from the given StringBuffer
void deleteChar(StringBuffer data, int pos) {
int len = data.length();
if (pos >= 0 && pos < len) {
String s = data.toString();
data.insert(0, s.substring(0, pos) + (pos < len -1 ? s.substring(pos + 1) : ""));
data.setLength(len - 1);
}
}
// Other utility methods
/**
* Returns the locale specific default decimal separator.
*/
public static char getDefaultDecimalSeparator () {
return defDecimalFormat.getDecimalFormatSymbols().getDecimalSeparator();
}
/**
* Returns the locale specific default grouping separator.
*/
public static char getDefaultGroupingSeparator () {
return defDecimalFormat.getDecimalFormatSymbols().getGroupingSeparator();
}
// Variables
boolean _commas = true ; // true to display thousands separator
boolean _currencySymbolLeads = true ; // true if currency symbol precedes value
boolean _ATMmode = false; // true for right-to-left data entry
String _currencySymbol = "$" ;
char _decimalPoint = getDefaultDecimalSeparator() ;
char _separator = getDefaultGroupingSeparator() ;
int _digitsAfterDecimal = 2 ;
private static DecimalFormat defDecimalFormat = (DecimalFormat) NumberFormat.getNumberInstance();
private static boolean debug = false ;
}