package org.openswing.swing.client;
import java.math.*;
import java.text.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import org.openswing.swing.logger.client.*;
import org.openswing.swing.util.client.*;
import java.beans.Beans;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import javax.swing.text.*;
/**
* <p>Title: OpenSwing Framework</p>
* <p>Description: Currency (numeric) input control.</p>
* <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
*
* <p> This file is part of OpenSwing Framework.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the (LGPL) Lesser General Public
* License as published by the Free Software Foundation;
*
* GNU LESSER GENERAL PUBLIC LICENSE
* Version 2.1, February 1999
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* The author may be contacted at:
* maurocarniel@tin.it</p>
*
* @author Mauro Carniel
* @version 1.0
*/
public class CurrencyControl extends BaseInputControl implements InputControl {
/** maximum number of digits */
private int maxCharacters = 255;
/** maximum value */
private double maxValue = Integer.MAX_VALUE;
/** minimum value; default value: 0 */
private double minValue = 0;
/** maximum number of decimals */
private int decimals = 0;
/** flag used to set thousands symbol visibility; default value = false */
private boolean grouping = false;
/** format for the text */
private DecimalFormat format = null;
/** value for an input control value not defined */
private String nullValue = null;
/** numeric field */
private CurrencyBox currencyBox = new CurrencyBox();
/** flag used to define whether zero digits (after decimal point) must be hided/showed; default value: <code>ClientSettings.ZERO_SHOWS_AS_ABSENT</code> i.e. show zero digits */
private boolean hideZeroDigits = ClientSettings.HIDE_ZERO_DIGITS;
/** currency symbol; default value: ClientSettings.getInstance().getResources().getCurrencySymbol() */
private String currencySymbol = ClientSettings.getInstance().getResources().getCurrencySymbol();
/** decimal symbol; default value: ClientSettings.getInstance().getResources().getDecimalSymbol() */
private char decimalSymbol = ClientSettings.getInstance().getResources().getDecimalSymbol();
/** grouping symbol; default value: ClientSettings.getInstance().getResources().getGroupingSymbol() */
private char groupingSymbol = ClientSettings.getInstance().getResources().getGroupingSymbol();
/** flag used to define the default position of currency symbol in currency control/column: on the left or on the right of the numeric value; default value: <code>ClientSettings.CURRENCY_SYMBOL_ON_LEFT</code> i.e. on the left of the numeric value */
private boolean currencySymbolOnLeft = ClientSettings.CURRENCY_SYMBOL_ON_LEFT;
/**
* Constructor.
*/
public CurrencyControl() {
this(10);
}
/**
* Constructor.
* @param columns number of visibile characters
*/
public CurrencyControl(int columns) {
currencyBox.setColumns(columns);
currencyBox.setDisabledTextColor(UIManager.getColor("TextField.foreground"));
// setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
// this.add(currencyBox);
this.setLayout(new GridBagLayout());
this.add(currencyBox, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
// this.setLayout(new java.awt.BorderLayout(0,0));
// this.add(currencyBox, java.awt.BorderLayout.CENTER);
StringBuffer s = new StringBuffer(currencyBox.getColumns()); for(int i=0;i<currencyBox.getColumns();i++) s.append("0");
setMinimumSize(new Dimension(
currencyBox.getFontMetrics(currencyBox.getFont()).stringWidth(s.toString()),
currencyBox.getPreferredSize().height
));
setFormat();
addKeyListener();
addFocusListener();
initListeners();
setGrouping(true);
}
/**
* Method called to set the numeric format.
*/
protected void setFormat() {
DecimalFormatSymbols dfs = new DecimalFormatSymbols();
dfs.setGroupingSeparator(groupingSymbol);
dfs.setDecimalSeparator(decimalSymbol);
String zero = "0";
if (hideZeroDigits)
zero = "#";
if (!isGrouping() && getDecimals()==0) {
if (currencySymbolOnLeft)
format = new DecimalFormat(currencySymbol+" "+"#");
else
format = new DecimalFormat("# "+currencySymbol);
}
else if (isGrouping() && getDecimals()==0) {
if (currencySymbolOnLeft)
format = new DecimalFormat(currencySymbol+" "+"#,###",dfs);
else
format = new DecimalFormat("#,### "+currencySymbol,dfs);
}
else if (isGrouping() && getDecimals()>0) {
String dec = "";
for(int i=0;i<getDecimals();i++)
dec += zero;
if (currencySymbolOnLeft)
format = new DecimalFormat(currencySymbol+" "+"#,##0."+dec,dfs);
else
format = new DecimalFormat("#,##0."+dec+" "+currencySymbol,dfs);
}
else if (!isGrouping() && getDecimals()>0) {
String dec = "";
for(int i=0;i<getDecimals();i++)
dec += zero;
if (currencySymbolOnLeft)
format = new DecimalFormat(currencySymbol+" "+"0."+dec,dfs);
else
format = new DecimalFormat("0."+dec+" "+currencySymbol,dfs);
}
format.setGroupingUsed(isGrouping());
if (currencySymbolOnLeft)
nullValue = currencySymbol+" ";
else
nullValue = " "+currencySymbol;
setText("");
}
/**
* @return currency symbol
*/
public final String getCurrencySymbol() {
return currencySymbol;
}
/**
* Set the currency symbol.
* @param currencySymbol currency symbol
*/
public final void setCurrencySymbol(String currencySymbol) {
Object val = getValue();
this.currencySymbol = currencySymbol;
if (!Beans.isDesignTime()) {
setFormat();
setValue(val);
}
}
/**
* @return decimal symbol
*/
public final char getDecimalSymbol() {
return decimalSymbol;
}
/**
* @return grouping symbol
*/
public final char getGroupingSymbol() {
return groupingSymbol;
}
/**
* Set decimal symbol.
* @param decimalSymbol decimal symbol
*/
public final void setDecimalSymbol(char decimalSymbol) {
Object val = getValue();
this.decimalSymbol = decimalSymbol;
if (!Beans.isDesignTime()) {
setFormat();
setValue(val);
}
}
/**
* Set grouping symbol.
* @param groupingSymbol grouping symbol
*/
public final void setGroupingSymbol(char groupingSymbol) {
Object val = getValue();
this.groupingSymbol = groupingSymbol;
if (!Beans.isDesignTime()) {
setFormat();
setValue(val);
}
}
/**
* Set maximum number of decimals.
* @param decimals maximum number of decimals
*/
public final void setDecimals(int decimals) {
Object val = getValue();
this.decimals = decimals;
if (!Beans.isDesignTime()) {
setFormat();
setValue(val);
}
}
/**
* @return flag used to define the default position of currency symbol in currency control/column: on the left or on the right of the numeric value
*/
public final boolean isCurrencySymbolOnLeft() {
return currencySymbolOnLeft;
}
/**
* Set the flag used to define the default position of currency symbol in currency control/column: on the left or on the right of the numeric value.
* Default value: <code>ClientSettings.CURRENCY_SYMBOL_ON_LEFT</code> i.e. on the left of the numeric value.
* @param currencySymbolOnLeft flag used to define the default position of currency symbol in currency control/column: on the left or on the right of the numeric value
*/
public final void setCurrencySymbolOnLeft(boolean currencySymbolOnLeft) {
this.currencySymbolOnLeft = currencySymbolOnLeft;
}
/**
* @return component inside this whose contains the value
*/
public JComponent getBindingComponent() {
return currencyBox;
}
/**
* @return BigDecimal value
*/
public final BigDecimal getBigDecimal() {
if (currencyBox.getText().length()==0 || currencyBox.getText().equals(nullValue))
return null;
try {
BigDecimal num = new BigDecimal(format.parse(currencyBox.getText()).doubleValue());
// if (String.valueOf(num.doubleValue()-(double)num.intValue()).length()>10)
num = num.setScale(decimals,BigDecimal.ROUND_HALF_UP);
return num;
}
catch (ParseException ex) {
Logger.error(this.getClass().getName(),"getBigDecimal","Error while creating BigDecimal object",ex);
return null;
}
}
/**
* @return Double value
*/
public final Double getDouble() {
if (currencyBox.getText().length()==0 || currencyBox.getText().equals(nullValue))
return null;
try {
return new Double(format.parse(currencyBox.getText()).doubleValue());
}
catch (ParseException ex) {
Logger.error(this.getClass().getName(),"getDouble","Error while creating BigDecimal object",ex);
return null;
}
}
/**
* @return BigDecimal value
*/
public final Object getValue() {
return getBigDecimal();
}
/**
* @return numeric value (without any currency symbol)
*/
public final String getText() {
BigDecimal value = null;
try {
if (currencyBox.getText().length()!=0 && !currencyBox.getText().trim().equals(nullValue.trim()) && !"-".equals(currencyBox.getText()))
value = new BigDecimal(format.parse(currencyBox.getText()).doubleValue());
if ("-".equals(currencyBox.getText()))
value = new BigDecimal(0).negate();
}
catch (ParseException ex) {
Logger.error(this.getClass().getName(),"getText","Error while creating BigDecimal object",ex);
return null;
}
if (value==null)
return null;
else
return String.valueOf(value.doubleValue());
}
/**
* Set maximum number of characters.
* @param maxCharacters maximum number of characters
*/
public void setMaxCharacters(int maxCharacters) {
this.maxCharacters = maxCharacters;
}
/**
* @return maximum number of decimals
*/
public int getDecimals() {
return decimals;
}
/**
* @return maximum value
*/
public double getMaxValue() {
return maxValue;
}
/**
* Set maximum value.
* @param maxValue maximum value
*/
public void setMaxValue(double maxValue) {
this.maxValue = maxValue;
}
/**
* @return minimum value
*/
public double getMinValue() {
return minValue;
}
/**
* Set minimum value.
* @param minValue minimum value
*/
public void setMinValue(double minValue) {
this.minValue = minValue;
}
/**
* @return maximum number of digits
*/
public int getMaxCharacters() {
return maxCharacters;
}
/**
* Set thousands symbol visibility.
* @param grouping thousands symbol visibility
*/
public final void setGrouping(boolean grouping) {
this.grouping = grouping;
setFormat();
}
/**
* @return boolean thousands symbol visibility
*/
public final boolean isGrouping() {
return grouping;
}
/**
* Create a key listener to correcly set the content of the control.
*/
private void addKeyListener() {
currencyBox.addKeyListener(new KeyAdapter() {
public final void keyReleased(KeyEvent e) {
e.consume();
}
public final void keyTyped(KeyEvent e) {
if (CurrencyControl.this.getText()==null ||
CurrencyControl.this.getText().length()==0)
CurrencyControl.this.setText(nullValue);
if (e.getKeyChar()==ClientSettings.getInstance().getResources().getDecimalSymbol() &&
decimals>0 &&
currencyBox.getText()!=null &&
currencyBox.getText().indexOf(ClientSettings.getInstance().getResources().getDecimalSymbol())!=-1) {
e.consume();
return;
}
if (e.getKeyChar()==ClientSettings.getInstance().getResources().getDecimalSymbol() && decimals>0)
return;
else if (e.getKeyChar()==ClientSettings.getInstance().getResources().getGroupingSymbol() && grouping)
return;
else if (e.getKeyChar()=='\b') {
if (currencySymbolOnLeft && currencyBox.getCaretPosition()<=nullValue.length())
e.consume();
else if (!currencySymbolOnLeft && currencyBox.getCaretPosition()>currencyBox.getText().length()-nullValue.length())
e.consume();
return;
}
else if (e.getKeyChar()=='-' && minValue<0 && currencyBox.getCaretPosition()==0 && currencyBox.getText()!=null && (currencyBox.getText().length()==0 || currencyBox.getText().charAt(0)!='-'))
return;
else if (e.getKeyChar()<'0' || e.getKeyChar()>'9')
e.consume();
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode()==e.VK_DELETE) {
if (currencySymbolOnLeft && currencyBox.getCaretPosition()<nullValue.length()) {
e.consume();
return;
}
else if (!currencySymbolOnLeft && currencyBox.getCaretPosition()>currencyBox.getText().length()-nullValue.length()) {
e.consume();
return;
}
}
if (e.getKeyCode()==e.VK_LEFT) {
if (currencySymbolOnLeft && currencyBox.getCaretPosition()<=nullValue.length()) {
e.consume();
return;
}
}
if (e.getKeyCode()==e.VK_RIGHT) {
if (!currencySymbolOnLeft && currencyBox.getCaretPosition()>=currencyBox.getText().length()-nullValue.length()) {
e.consume();
return;
}
}
if (e.getKeyCode()==e.VK_ENTER ||
e.getKeyCode()==e.VK_TAB ||
e.getKeyCode()==e.VK_DELETE ||
e.getKeyCode()==e.VK_BACK_SPACE ||
e.getKeyCode()==e.VK_LEFT ||
e.getKeyCode()==e.VK_ESCAPE ||
e.getKeyCode()==e.VK_RIGHT ||
e.isAltDown())
return;
e.consume();
}
});
}
/**
* Method that overrides the super class setText method.
* @param text number to set
*/
public final void setText(String text) {
try {
BigDecimal number = null;
if (text != null && text.length() > 0) {
number = new BigDecimal(text);
}
if (number!=null)
currencyBox.setText(format.format(number));
else
currencyBox.setText(nullValue);
}
catch (Exception ex) {
currencyBox.setText(nullValue);
}
}
/**
* @param value number to set
*/
public final void setValue(Object value) {
try {
if (value==null)
currencyBox.setText(nullValue);
else
currencyBox.setText(format.format(value));
}
catch (Exception ex) {
currencyBox.setText(nullValue);
}
}
/**
* Create a focus listener to correcly set the content of the control.
*/
private void addFocusListener() {
currencyBox.addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent e) {
if (currencyBox.isEnabled() && currencyBox.isEditable()) {
try {
Number number = format.parse(currencyBox.getText());
if (number!=null) {
if (number.doubleValue()>maxValue) {
setText(String.valueOf(maxValue));
number = new BigDecimal(maxValue);
}
else if (number.doubleValue()<minValue) {
setText(String.valueOf(minValue));
number = new BigDecimal(minValue);
}
else
setText(String.valueOf(number));
// test number of decimals...
java.math.BigDecimal d = new java.math.BigDecimal(number.doubleValue());
d = d.setScale(decimals,java.math.BigDecimal.ROUND_HALF_UP);
setText(d.toString());
}
}
catch (ParseException ex) {
setText(nullValue);
}
if (currencyBox.getText().length()>maxCharacters)
CurrencyControl.this.setText(currencyBox.getText().substring(0,maxCharacters));
}
}
});
}
/**
* @return flag used to define whether zero digits (after decimal point) must be hided/showed
*/
public final boolean isHideZeroDigits() {
return hideZeroDigits;
}
/**
* Define whether zero digits (after decimal point) must be hided/showed; default value: <code>false</code> i.e. show zero digits.
* @param hideZeroDigits flag used to define whether zero digits (after decimal point) must be hided/showed
*/
public final void setHideZeroDigits(boolean hideZeroDigits) {
this.hideZeroDigits = hideZeroDigits;
}
/**
* Replace enabled setting with editable setting (this allow tab swithing).
* @param enabled flag used to set abilitation of control
*/
public void setEnabled(boolean enabled) {
currencyBox.setEditable(enabled);
currencyBox.setFocusable(enabled || ClientSettings.DISABLED_INPUT_CONTROLS_FOCUSABLE);
if (!enabled)
currencyBox.setBackground((Color)UIManager.get("TextField.inactiveBackground"));
}
/**
* @return current input control abilitation
*/
public final boolean isEnabled() {
try {
return currencyBox.isEditable();
}
catch (Exception ex) {
return false;
}
}
/**
* Set input control visible characters.
* @param columns visible characters
*/
public final void setColumns(int columns) {
currencyBox.setColumns(columns);
StringBuffer s = new StringBuffer(currencyBox.getColumns()); for(int i=0;i<currencyBox.getColumns();i++) s.append("0");
setMinimumSize(new Dimension(
currencyBox.getFontMetrics(currencyBox.getFont()).stringWidth(s.toString()),
currencyBox.getPreferredSize().height
));
}
/**
* @return input control visibile characters
*/
public final int getColumns() {
return currencyBox.getColumns();
}
/**
* Adds the specified focus listener to receive focus events from
* this component when this component gains input focus.
* If listener <code>l</code> is <code>null</code>,
* no exception is thrown and no action is performed.
*
* @param l the focus listener
* @see java.awt.event.FocusEvent
* @see java.awt.event.FocusListener
* @see #removeFocusListener
* @see #getFocusListeners
* @since JDK1.1
*/
public final void addFocusListener(FocusListener listener) {
try {
currencyBox.addFocusListener(listener);
}
catch (Exception ex) {
}
}
/**
* Removes the specified focus listener so that it no longer
* receives focus events from this component. This method performs
* no function, nor does it throw an exception, if the listener
* specified by the argument was not previously added to this component.
* If listener <code>l</code> is <code>null</code>,
* no exception is thrown and no action is performed.
*
* @param l the focus listener
* @see java.awt.event.FocusEvent
* @see java.awt.event.FocusListener
* @see #addFocusListener
* @see #getFocusListeners
* @since JDK1.1
*/
public final void removeFocusListener(FocusListener listener) {
try {
currencyBox.removeFocusListener(listener);
}
catch (Exception ex) {
}
}
/**
* Adds the specified action listener to receive
* action events from this textfield.
*
* @param l the action listener to be added
*/
public final void addActionListener(ActionListener listener) {
try {
currencyBox.addActionListener(listener);
}
catch (Exception ex) {
}
}
/**
* Adds the specified key listener to receive
* action events from this field.
*
* @param l the key listener to be added
*/
public final void addKeyListener(KeyListener listener) {
try {
currencyBox.addKeyListener(listener);
}
catch (Exception ex) {
}
}
public void processKeyEvent(KeyEvent e) {
currencyBox.processKeyEvent(e);
}
/**
* Removes the specified action listener so that it no longer
* receives action events from this field.
*
* @param l the action listener to be removed
*/
public final void removeActionListener(ActionListener listener) {
try {
currencyBox.removeActionListener(listener);
}
catch (Exception ex) {
}
}
/**
* Removes the specified key listener so that it no longer
* receives action events from this field.
*
* @param l the key listener to be removed
*/
public final void removeKeyListener(KeyListener listener) {
try {
currencyBox.removeKeyListener(listener);
}
catch (Exception ex) {
}
}
/**
* Selects the text between the specified start and end positions.
* <p>
* This method sets the start and end positions of the
* selected text, enforcing the restriction that the start position
* must be greater than or equal to zero. The end position must be
* greater than or equal to the start position, and less than or
* equal to the length of the text component's text.
* <p>
* If the caller supplies values that are inconsistent or out of
* bounds, the method enforces these constraints silently, and
* without failure. Specifically, if the start position or end
* position is greater than the length of the text, it is reset to
* equal the text length. If the start position is less than zero,
* it is reset to zero, and if the end position is less than the
* start position, it is reset to the start position.
* <p>
* This call is provided for backward compatibility.
* It is routed to a call to <code>setCaretPosition</code>
* followed by a call to <code>moveCaretPosition</code>.
* The preferred way to manage selection is by calling
* those methods directly.
*
* @param selectionStart the start position of the text
* @param selectionEnd the end position of the text
* @see #setCaretPosition
* @see #moveCaretPosition
*/
public final void select(int selectionStart,int selectionEnd) {
currencyBox.select(selectionStart,selectionEnd);
}
/**
* <p>Title: OpenSwing Framework</p>
* <p>Description: Inner class used to redirect key event to the inner JTextField.</p>
* <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
* @author Mauro Carniel
* @version 1.0
*/
class CurrencyBox extends JTextField {
public CurrencyBox() {
this.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
}
public void insertUpdate(DocumentEvent e) {
if (!currencySymbolOnLeft && getCaretPosition()>=getText().length()-nullValue.length()) {
// currency symbol must be showed on the right of numeric value and
// user attempts to write on the right of currency symbol
try {
final BigDecimal n = new BigDecimal(getText().substring(0,getText().length()-nullValue.length()));
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setText(n+nullValue);
setCaretPosition(getText().length()-nullValue.length());
}
});
}
catch (Exception ex1) {
}
}
if (!currencySymbolOnLeft && getText().startsWith(nullValue))
// currency symbol must be showed on the right of numeric value and
// current numeric value is null and user attempts to write the first digit:
// without rewriting just editing data, it would be write on the right of the text field and
// this is not correct...
try {
final BigDecimal n = new BigDecimal(getText().substring(nullValue.length()));
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setText(n+nullValue);
setCaretPosition(getText().length()-nullValue.length());
}
});
}
catch (Exception ex1) {
}
}
public void removeUpdate(DocumentEvent e) {
}
});
}
public String getText() {
String t = super.getText();
if (t!=null &&
!currencySymbolOnLeft &&
t.indexOf(" ")>0)
t = t.substring(0,t.indexOf(" "))+nullValue;
return t;
}
public void setCaretPosition(int pos) {
if (currencySymbolOnLeft && pos<nullValue.length())
super.setCaretPosition(nullValue.length());
else if (!currencySymbolOnLeft && pos>=getText().length()-nullValue.length())
super.setCaretPosition(getText().length()-nullValue.length());
else
super.setCaretPosition(pos);
}
public void processKeyEvent(KeyEvent e) {
super.processKeyEvent(e);
}
}
}