com.github.lgooddatepicker.zinternaltools.JIntegerTextField Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of LGoodDatePicker Show documentation
Show all versions of LGoodDatePicker Show documentation
Java 8 Swing Date Picker. Easy to use, good looking, nice features, and
localized. Uses the JSR-310 standard.
package com.github.lgooddatepicker.zinternaltools;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
/**
* JIntegerTextField,
*
* This implements a text field where it is only possible to type numbers into the field. The field
* will contain a valid integer at all times. The component has methods to get or set the value of
* the field as an integer, which will never throw a parsing exception. This component does not
* allow the field to be empty. The range of allowed numbers can be set by the programmer, within
* limits that are described below.
*
* The default range for this component is (Integer.MIN_VALUE to Integer.MAX_VALUE). You may
* optionally set a different minimum and maximum value for the number, within certain limits.
* Specifically, the chosen range must include all of the single-digit numbers from 1 through 9. For
* details on why this requirement is necessary for this component, see the "Single Digit
* Requirement Notes" below. If your usage requires a range outside of these specifications, then a
* JSpinner might be a possible alternative to consider.
*
* Single Digit Requirement Notes: The minimum and maximum values for this component must include
* the numbers 1 through 9, because otherwise this component would require a "commit or revert" type
* of functionality to handle all cases. For example, imagine the field is blank, the minimum value
* is 100, and a user types a "5". A JSpinner handles this situation by implementing a focus
* listener, and "reverting" to the minimum value of 100 if the component loses focus while it is in
* an invalid state. This component prevents the invalid state from being created in the first
* place. To summarize, allowing invalid states (or commit and revert) is outside the intended scope
* of this component.
*/
public class JIntegerTextField extends JTextField {
private int maximumValue = Integer.MAX_VALUE;
private int minimumValue = Integer.MIN_VALUE;
public IntegerTextFieldNumberChangeListener numberChangeListener = null;
public boolean skipNotificationOfNumberChangeListenerWhileTrue = false;
public JIntegerTextField() {
this(10);
}
public JIntegerTextField(int preferredWidthFromColumnCount) {
super(preferredWidthFromColumnCount);
setText("" + getDefaultValue());
selectAll();
AbstractDocument document = (AbstractDocument) this.getDocument();
document.setDocumentFilter(new IntegerFilter(this));
getDocument().addDocumentListener(new NumberListener());
}
private boolean allowNegativeNumbers() {
return (minimumValue < 0);
}
public int getDefaultValue() {
return (minimumValue > 0) ? 1 : 0;
}
public int getMaximumValue() {
return maximumValue;
}
public int getMinimumValue() {
return minimumValue;
}
public int getValue() {
String text = getText();
if (text == null || text.isEmpty()) {
return 0;
}
int number;
try {
number = Integer.parseInt(text);
} catch (Exception e) {
throw new RuntimeException("JIntegerTextField.getValue(), "
+ "The text value could not be parsed. This should never happen.");
}
return number;
}
public static void main(String[] args) {
final JIntegerTextField integerTextField = new JIntegerTextField();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
integerTextField.runDemo();
}
});
// SwingUtilities.invokeLater(integerTextField::runDemo);
}
private void notifyListenerIfNeeded() {
if (skipNotificationOfNumberChangeListenerWhileTrue) {
return;
}
if (numberChangeListener != null) {
Integer integer = getValidIntegerOrNull(getText());
if (integer != null) {
numberChangeListener.integerTextFieldNumberChanged(this, integer);
}
}
}
private void runDemo() {
JFrame frame = new JFrame();
frame.setLayout(new GridBagLayout());
frame.setSize(300, 300);
frame.add(this);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public void setMaximumValue(int maximumValue) {
this.maximumValue = (maximumValue >= 9) ? maximumValue : 9;
}
public void setMinimumValue(int minimumValue) {
this.minimumValue = (minimumValue <= 1) ? minimumValue : 1;
}
public void setValue(int value) {
value = (value < minimumValue) ? minimumValue : value;
value = (value > maximumValue) ? maximumValue : value;
setText("" + value);
}
private boolean isValidInteger(String text) {
return (getValidIntegerOrNull(text) != null);
}
private Integer getValidIntegerOrNull(String text) {
int number;
try {
number = Integer.parseInt(text);
} catch (NumberFormatException e) {
return null;
}
if (number < minimumValue || number > maximumValue) {
return null;
}
if (!allowNegativeNumbers() && text.contains("-")) {
return null;
}
return number;
}
private class NumberListener implements DocumentListener {
@Override
public void insertUpdate(DocumentEvent e) {
notifyListenerIfNeeded();
}
@Override
public void removeUpdate(DocumentEvent e) {
notifyListenerIfNeeded();
}
@Override
public void changedUpdate(DocumentEvent e) {
notifyListenerIfNeeded();
}
}
private class IntegerFilter extends DocumentFilter {
public IntegerFilter(JIntegerTextField parentField) {
if (parentField == null) {
throw new RuntimeException("IntegerTextField.IntegerFilter, "
+ "The parent text field cannot be null.");
}
this.parentField = parentField;
}
private JIntegerTextField parentField;
private boolean skipFiltersWhileTrue = false;
@Override
public void remove(DocumentFilter.FilterBypass fb, int offset, int length)
throws BadLocationException {
if (skipFiltersWhileTrue) {
super.remove(fb, offset, length);
return;
}
String oldText = fb.getDocument().getText(0, fb.getDocument().getLength());
StringBuilder newTextBuilder = new StringBuilder(oldText);
newTextBuilder.delete(offset, (offset + length));
String newText = newTextBuilder.toString();
if (newText.trim().isEmpty() || oldText.equals("-1")) {
setFieldToDefaultValue();
} else if (allowNegativeNumbers() && newText.trim().equals("-")) {
setFieldToNegativeOne();
} else if (isValidInteger(newText)) {
super.remove(fb, offset, length);
} else {
Toolkit.getDefaultToolkit().beep();
}
}
@Override
public void replace(FilterBypass fb, int offset, int length, String newChars, AttributeSet a)
throws BadLocationException {
if (skipFiltersWhileTrue) {
super.replace(fb, offset, length, newChars, a);
return;
}
int oldTextLength = fb.getDocument().getLength();
String oldText = fb.getDocument().getText(0, oldTextLength);
StringBuilder newTextBuilder = new StringBuilder(oldText);
newTextBuilder.delete(offset, (offset + length));
newTextBuilder.insert(offset, newChars);
String newText = newTextBuilder.toString();
if (newText.trim().isEmpty()) {
setFieldToDefaultValue();
} else if (allowNegativeNumbers() && newText.trim().equals("-")) {
setFieldToNegativeOne();
} else if (length == oldTextLength && isValidInteger(newText.trim())) {
// If the entire document is being replaced, allow a trimmed replacement of
// integers that originally included surrounding whitespace.
// (This makes it easier to paste a number from the clipboard.)
super.replace(fb, 0, length, newText.trim(), a);
} else if (isValidInteger(newText)) {
super.replace(fb, offset, length, newChars, a);
} else {
Toolkit.getDefaultToolkit().beep();
}
}
@Override
public void insertString(FilterBypass fb, int offset, String newChars,
AttributeSet a) throws BadLocationException {
if (skipFiltersWhileTrue) {
super.insertString(fb, offset, newChars, a);
return;
}
String oldText = fb.getDocument().getText(0, fb.getDocument().getLength());
StringBuilder newTextBuilder = new StringBuilder(oldText);
newTextBuilder.insert(offset, newChars);
String newText = newTextBuilder.toString();
if (newText.trim().isEmpty()) {
setFieldToDefaultValue();
} else if (allowNegativeNumbers() && newText.trim().equals("-")) {
setFieldToNegativeOne();
} else if (isValidInteger(newText)) {
super.insertString(fb, offset, newChars, a);
} else {
Toolkit.getDefaultToolkit().beep();
}
}
private void setFieldToDefaultValue() {
skipFiltersWhileTrue = true;
String defaultValue = "" + parentField.getDefaultValue();
parentField.setText(defaultValue);
parentField.selectAll();
skipFiltersWhileTrue = false;
}
private void setFieldToNegativeOne() {
skipFiltersWhileTrue = true;
parentField.setText("-1");
parentField.select(1, 2);
skipFiltersWhileTrue = false;
}
}
public interface IntegerTextFieldNumberChangeListener {
public void integerTextFieldNumberChanged(JIntegerTextField source, int newValue);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy