home *** CD-ROM | disk | FTP | other *** search
Java Source | 1998-12-09 | 13.9 KB | 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 ;
- }
-