/* * Copyright (c) 2002-2015, JIDE Software Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package jidefx.scene.control.field; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.HPos; import javafx.geometry.VPos; import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.IndexRange; import javafx.scene.control.TextField; import javafx.util.Callback; import jidefx.scene.control.decoration.DecorationDelegate; import jidefx.scene.control.decoration.DecorationSupport; import jidefx.scene.control.decoration.DecorationUtils; import jidefx.scene.control.decoration.Decorator; import jidefx.scene.control.decoration.PredefinedDecorators; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Map; /** * {@code MaskTextField} is a {@code TextField} that can restrict the user input by applying a mask. * <p> * <b >Position-based Mask</b> * <p> * Position-base mask works for all the editing cases when the input string has a fixed length and each character can be * restricted based on its position. To address this case, we allow you define the masks on a MaskTextField. * <p> * <b >InputMask</b> * <p> * MaskTextField has setInputMask(String mask) to set the mask we mentioned above. The pre-defined mask characters are: * <ul> <li>A INPUT_MASK_LETTER ASCII alphabetic character required. A-Z, a-z. <li>N INPUT_MASK_DIGIT_OR_LETTER ASCII * alphanumeric character required. A-Z, a-z, 0-9.</li> <li>X INPUT_MASK_ANY_NON_SPACE Any character required except * spaces. <li>H INPUT_MASK_HAX Hexadecimal character required. A-F, a-f, 0-9. <li>D INPUT_MASK_DIGIT_NON_ZERO ASCII * digit required. 1-9. <li>9 INPUT_MASK_DIGIT ASCII digit required. 0-9. <li>8 INPUT_MASK_DIGIT_0_TO_8 ASCII digit * required. 0-8. <li>7, 6, 5, 4, 3, 2 and so on which means only allows from 0 to that number * <li>2 INPUT_MASK_DIGIT_0_TO_2 ASCII digit required. 0-2. <li>1 INPUT_MASK_DIGIT_0_TO_1 ASCII digit required. 0-1, for * example, a binary number <li>0 INPUT_MASK_DIGIT_ZERO 0 required <li>G INPUT_MASK_DIGIT_GROUP Indicates a Group. This * is the special one, we will discuss it later </ul> If the setInitialText is not called, the InputMask will be used to * create the initial text for the field. We create it by removing all masks, replace them with spaces or the specified * placeholder character, and leave the non-mask characters where they are. The placeholder character can be set using * setPlaceholderCharacter(char). See below. In both cases, the InputMask is 999-99-9999. * <p> * <b>ConversionMask</b> * <p> * To make the grammar easy to understand, we strictly enforced that there is only one mask character per position for * the InputMask. But clearly, one character is not enough in some cases. So we also allow you to define several other * masks for other purposes. In addition to the Input Mask, we also have a separate Conversion Mask which will * automatically convert the entered character to another character. This mask is optional. It can be set using * setConversionMask(String mask). If not set, there will be no conversion. If you ever set the mask, please make sure * they have the exact same length as the InputMask, and have a valid conversion mask character at the exact position * where there is an InputMask character. <ul> <li>U CONVERSION_MASK_UPPER_CASE Uppercase required. If user enters a * lowercase letter, it will be automatically converted to the corresponding uppercase letter. * <li>L CONVERSION_MASK_LOWER_CASE Lowercase required. If user enters an uppercase letter, it will be automatically * converted to the corresponding lowercase letter. <li>Any other undefined chars CONVERSION_MASK_IGNORE No conversion * </ul> * <p> * {@code RequiredMask} * <p> * The RequiredMask is to indicate whether the character on a particular position is required. It is again optional. It * can be set using setRequiredMask(String mask). If not set, a valid non-space character is required on all the * positions. If you ever set the mask, please make sure they have the same length as the InputMask, and have a valid * required mask character at the exact position where there is an InputMask character. <ul> * <li>R REQUIRED_MASK_REQUIRED Required. Users must enter a valid character that matches with the mask on this * position. <li>Any other undefined chars REQUIRED_MASK_NOT_REQUIRED Not required. User can enter a space at this * position. </ul> */ @SuppressWarnings({"Convert2Lambda", "SpellCheckingInspection"}) public class MaskTextField extends TextField implements DecorationSupport { private static final String STYLE_CLASS_DEFAULT = "mask-combo-box"; //NON-NLS public static final char INPUT_MASK_LETTER = 'A'; public static final char INPUT_MASK_DIGIT_OR_LETTER = 'N'; public static final char INPUT_MASK_ANY_NON_SPACE = 'X'; public static final char INPUT_MASK_HAX = 'H'; public static final char INPUT_MASK_DIGIT_NON_ZERO = 'D'; public static final char INPUT_MASK_DIGIT = '9'; public static final char INPUT_MASK_DIGIT_0_TO_8 = '8'; public static final char INPUT_MASK_DIGIT_0_TO_7 = '7'; public static final char INPUT_MASK_DIGIT_0_TO_6 = '6'; public static final char INPUT_MASK_DIGIT_0_TO_5 = '5'; public static final char INPUT_MASK_DIGIT_0_TO_4 = '4'; public static final char INPUT_MASK_DIGIT_0_TO_3 = '3'; public static final char INPUT_MASK_DIGIT_0_TO_2 = '2'; public static final char INPUT_MASK_DIGIT_0_TO_1 = '1'; public static final char INPUT_MASK_DIGIT_ZERO = '0'; public static final char CONVERSION_MASK_UPPER_CASE = 'U'; public static final char CONVERSION_MASK_LOWER_CASE = 'L'; public static final char CONVERSION_MASK_IGNORE = '_'; public static final char REQUIRED_MASK_REQUIRED = 'R'; public static final char REQUIRED_MASK_NOT_REQUIRED = '_'; private StringProperty _inputMaskProperty; private StringProperty _requiredMaskProperty; private StringProperty _conversionMaskProperty; private StringProperty _validCharactersProperty; private StringProperty _invalidCharactersProperty; private ObjectProperty<Character> _placeholderCharacterProperty; private StringProperty _initialTextProperty; private BooleanProperty _autoAdvanceProperty; private BooleanProperty _clearbuttonVisibleProperty; private Decorator<Button> _clearButtonDecorator; private ObservableMap<Character, Callback<Character, Boolean>> _maskVerifiers; private ObservableMap<Character, Callback<Character, Character>> _conversions; private String _fixedText; public MaskTextField() { initializeTextField(); initializeStyle(); registerListeners(); } public MaskTextField(String text) { super(text); initializeTextField(); initializeStyle(); registerListeners(); } /** * Adds or removes style from the getStyleClass. Subclass should call super if you want to keep the existing * styles. */ protected void initializeStyle() { getStyleClass().addAll(STYLE_CLASS_DEFAULT, DecorationSupport.STYLE_CLASS_DECORATION_SUPPORT); } /** * Initializes the text field. We will initialize the inputMaskVerifiers and conversions in this method. */ protected void initializeTextField() { initializeInputMaskVerifiers(); initializeConversions(); // setContentFilter(new Callback<ContentChange, ContentChange>() { // @Override // public ContentChange call(ContentChange param) { // if (getInputMask() != null) { // int start = param.getStart(); // int caret = start; // int end = param.getEnd(); // could be adjusted // // int totalLength = getInputMask().length(); // // StringBuilder newText = new StringBuilder(); // buffer for new text. we will append one by one after verify and convert it // // String text = param.getText(); // input text in this ContentChange // int index = 0; // while (caret < totalLength) { // if (index >= text.length()) // break; // char c = text.charAt(index); // get the char from the text. // if (verifyChar(c, caret) || c == getPlaceholderCharacter()) { // newText.append(convert(c, caret)); // // TODO: replace by default unless it is at the end // index++; // move to the next char in the text // caret++; // } // else { // // auto-advance // if (isAutoAdvance() && (caret < getFixedText().length() && getFixedText().charAt(caret) == c)) { // typed a fixed text // newText.append(c); // caret++; // } // else if (isAutoAdvance() && (caret < getText().length() && getText().charAt(caret) != getPlaceholderCharacter() && getFixedText().contains("" + getText().charAt(caret)))) { // fixed text already there // newText.append(getText().charAt(caret)); // caret++; // } // else if (isAutoAdvance() && (caret < getFixedText().length() && getFixedText().charAt(caret) != getPlaceholderCharacter())) { // next one is a fixed text so we automatically append the fixed text and continue // newText.append(getFixedText().charAt(caret)); // caret++; // } // else { // break; // } // } // } // // only break when there is no new text and we are done with the count that we are expected to do // // if there are still new text, we will continue even when the count is done // String s = newText.toString(); // if (s.length() > 0) { // param.setText(s); // param.setEnd(Math.min(caret, getText().length())); // param.setNewAnchor(caret); // param.setNewCaretPosition(caret); // } // else { // String existingText = getText(start, end); // param.setText(keepFixedText(existingText)); // param.setNewAnchor(end); // param.setNewCaretPosition(end); // } // } // return param; // } // }); } @SuppressWarnings("Convert2MethodRef") protected void initializeInputMaskVerifiers() { Map<Character, Callback<Character, Boolean>> maskVerifiers = getInputMaskVerifiers(); maskVerifiers.clear(); maskVerifiers.put(INPUT_MASK_LETTER, c -> Character.isLetter(c)); maskVerifiers.put(INPUT_MASK_DIGIT_OR_LETTER, c -> Character.isLetterOrDigit(c)); maskVerifiers.put(INPUT_MASK_ANY_NON_SPACE, c -> !Character.isSpaceChar(c)); maskVerifiers.put(INPUT_MASK_HAX, c -> ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || Character.isDigit(c))); maskVerifiers.put(INPUT_MASK_DIGIT_NON_ZERO, c -> Character.isDigit(c) && c != '0'); maskVerifiers.put(INPUT_MASK_DIGIT, c -> Character.isDigit(c)); maskVerifiers.put(INPUT_MASK_DIGIT_0_TO_8, c -> (c >= '0' && c <= '8')); maskVerifiers.put(INPUT_MASK_DIGIT_0_TO_7, c -> (c >= '0' && c <= '7')); maskVerifiers.put(INPUT_MASK_DIGIT_0_TO_6, c -> (c >= '0' && c <= '6')); maskVerifiers.put(INPUT_MASK_DIGIT_0_TO_5, c -> (c >= '0' && c <= '5')); maskVerifiers.put(INPUT_MASK_DIGIT_0_TO_4, c -> (c >= '0' && c <= '4')); maskVerifiers.put(INPUT_MASK_DIGIT_0_TO_3, c -> (c >= '0' && c <= '3')); maskVerifiers.put(INPUT_MASK_DIGIT_0_TO_2, c -> (c >= '0' && c <= '2')); maskVerifiers.put(INPUT_MASK_DIGIT_0_TO_1, c -> (c >= '0' && c <= '1')); maskVerifiers.put(INPUT_MASK_DIGIT_ZERO, c -> (c == '0')); } /** * Gets the verifiers for the InputMask. The verifier is a callback which will verify the input char and return true * or false. True means the input char is valid at the position, false means invalid. * * @return the verifiers for the InputMask. */ public ObservableMap<Character, Callback<Character, Boolean>> getInputMaskVerifiers() { if (_maskVerifiers == null) { _maskVerifiers = FXCollections.observableHashMap(); } return _maskVerifiers; } @SuppressWarnings("Convert2MethodRef") protected void initializeConversions() { Map<Character, Callback<Character, Character>> conversions = getConversions(); conversions.clear(); conversions.put(CONVERSION_MASK_UPPER_CASE, c -> Character.toUpperCase(c)); conversions.put(CONVERSION_MASK_LOWER_CASE, c -> Character.toLowerCase(c)); } /** * Gets the conversions. The conversion is a callback which will convert an input char to another char. * * @return the conversions. */ public ObservableMap<Character, Callback<Character, Character>> getConversions() { if (_conversions == null) { _conversions = FXCollections.observableHashMap(); } return _conversions; } protected void registerListeners() { InvalidationListener verifierListener = new InvalidationListener() { @Override public void invalidated(Observable observable) { clear(); } }; getInputMaskVerifiers().addListener(verifierListener); editableProperty().addListener(new ChangeListener<Boolean>() { private final String comboBoxStyleClass = "combo-box-field"; //NON-NLS private final String textInputStyleClass = "text-input"; //NON-NLS @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { if (newValue) { getStyleClass().remove(comboBoxStyleClass); getStyleClass().add(textInputStyleClass); } else { getStyleClass().add(comboBoxStyleClass); getStyleClass().remove(textInputStyleClass); } } }); } /** * Clears the text if any. */ @Override public void clear() { setTextWithoutChecking(getInitialText() != null ? getInitialText() : getInitialTextFromMask()); } boolean enforcing = true; private void setTextWithoutChecking(String text) { try { enforcing = false; setText(text); } finally { enforcing = true; } } private String getFixedText() { if (_fixedText == null) { _fixedText = getInitialTextFromMask(); } return _fixedText; } private String getInitialTextFromMask() { String mask = getInputMask(); if (mask != null) { Map<Character, Callback<Character, Boolean>> maskVerifiers = getInputMaskVerifiers(); for (Character c : maskVerifiers.keySet()) { mask = mask.replace(c, getPlaceholderCharacter()); } } return mask; } public ObjectProperty<Character> placeholderCharacterProperty() { if (_placeholderCharacterProperty == null) { _placeholderCharacterProperty = new SimpleObjectProperty<Character>(this, "placeholderCharacter", ' ') { //NON-NLS @Override protected void invalidated() { super.invalidated(); clear(); } }; } return _placeholderCharacterProperty; } /** * Gets the placeholder character. This is used fill in the spaces for the masks when the text is not entered yet. * By default, we will use whitespaces. * * @return the placeholder character. */ public char getPlaceholderCharacter() { return placeholderCharacterProperty().get(); } /** * Sets the placeholder character. * * @param placeholderCharacter a new placeholder character. */ public void setPlaceholderCharacter(char placeholderCharacter) { placeholderCharacterProperty().set(placeholderCharacter); } public StringProperty inputMaskProperty() { if (_inputMaskProperty == null) { _inputMaskProperty = new SimpleStringProperty(this, "inputMask") { //NON-NLS @Override protected void invalidated() { super.invalidated(); clear(); } }; } return _inputMaskProperty; } /** * Gets the input mask. * * @return the input mask. */ public String getInputMask() { return inputMaskProperty().get(); } /** * Sets the input mask. * * @param inputMask a new input mask */ public void setInputMask(String inputMask) { inputMaskProperty().set(inputMask); } public StringProperty requiredMaskProperty() { if (_requiredMaskProperty == null) { _requiredMaskProperty = new SimpleStringProperty(this, "requiredMask"); //NON-NLS } return _requiredMaskProperty; } /** * Gets the required mask. * * @return the required mask. */ public String getRequiredMask() { return requiredMaskProperty().get(); } /** * Sets the required mask. * * @param requiredMask a new required mask */ public void setRequiredMask(String requiredMask) { requiredMaskProperty().set(requiredMask); } public StringProperty conversionMaskProperty() { if (_conversionMaskProperty == null) { _conversionMaskProperty = new SimpleStringProperty(this, "conversionMask"); //NON-NLS } return _conversionMaskProperty; } /** * Gets the conversion mask. * * @return the conversion mask. */ public String getConversionMask() { return conversionMaskProperty().get(); } /** * Sets the conversion mask. * * @param mask a new conversion mask */ public void setConversionMask(String mask) { conversionMaskProperty().set(mask); } public StringProperty validCharactersProperty() { if (_validCharactersProperty == null) { _validCharactersProperty = new SimpleStringProperty(this, "validCharacters"); //NON-NLS } return _validCharactersProperty; } /** * Gets the valid characters. * * @return the valid characters. */ public String getValidCharacters() { return validCharactersProperty().get(); } /** * Sets the valid characters. * * @param validCharacters a new set of valid characters */ public void setValidCharacters(String validCharacters) { validCharactersProperty().set(validCharacters); } public StringProperty invalidCharactersProperty() { if (_invalidCharactersProperty == null) { _invalidCharactersProperty = new SimpleStringProperty(this, "invalidCharacters"); //NON-NLS } return _invalidCharactersProperty; } /** * Gets the invalid characters. * * @return the invalid characters. */ public String getInvalidCharacters() { return invalidCharactersProperty().get(); } /** * Sets the invalid characters. * * @param invalidCharacters a new set of invalid characters */ public void setInvalidCharacters(String invalidCharacters) { invalidCharactersProperty().set(invalidCharacters); } public StringProperty initialTextProperty() { if (_initialTextProperty == null) { _initialTextProperty = new SimpleStringProperty(this, "initialText") { //NON-NLS @Override protected void invalidated() { super.invalidated(); clear(); } }; } return _initialTextProperty; } /** * Gets the initial text. It will be used at the initial text on the field if setText was never called. * * @return the initial text. */ public String getInitialText() { return initialTextProperty().get(); } /** * Sets the initial text. * * @param mask the initial text. */ public void setInitialText(String mask) { initialTextProperty().set(mask); } public BooleanProperty autoAdvanceProperty() { if (_autoAdvanceProperty == null) { _autoAdvanceProperty = new SimpleBooleanProperty(this, "autoAdvance", true); //NON-NLS } return _autoAdvanceProperty; } /** * Checks if the auto-advance flag. If true, the caret will move to the next position if the typed letter is not * valid at the current position. We will check the next position to find out if the typed letter is valid. If not, * we will continue util finding a valid position for the letter, or reaching the end. * * @return the auto-advance flag. Default is true. */ public boolean isAutoAdvance() { return autoAdvanceProperty().get(); } /** * Sets the auto-advance flag. * * @param autoAdvance a new auto-advance flag */ public void setAutoAdvance(boolean autoAdvance) { autoAdvanceProperty().set(autoAdvance); } private String keepFixedText(String text) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < text.length(); i++) { String s = text.substring(i, i + 1); if (getFixedText().contains(s)) { buf.append(s); } else { buf.append(getPlaceholderCharacter()); } } return buf.toString(); } private char convert(char c, int index) { String conversionMask = getConversionMask(); if (Character.isSpaceChar(c) && getInputMaskVerifiers().containsKey(getInputMask().charAt(index))) return getPlaceholderCharacter(); if (conversionMask != null && index < conversionMask.length()) { Callback<Character, Character> callback = getConversions().get(conversionMask.charAt(index)); if (callback != null) { return callback.call(c); } } return c; } private boolean verifyChar(char c, int index) { String inputMask = getInputMask(); if (inputMask != null && index < inputMask.length()) { // if it is a space, accept it if the requiredMask is defined and it said not required char maskChar = inputMask.charAt(index); if (maskChar == c) return true; if (Character.isSpaceChar(c) || c == getPlaceholderCharacter()) { String requiredMask = getRequiredMask(); if (requiredMask != null && index < requiredMask.length()) { return requiredMask.charAt(index) == REQUIRED_MASK_NOT_REQUIRED; } } Callback<Character, Boolean> callback = getInputMaskVerifiers().get(maskChar); return (getValidCharacters() == null || getValidCharacters().trim().isEmpty() || getValidCharacters().contains("" + convert(c, index))) && (getInvalidCharacters() == null || getInvalidCharacters().trim().isEmpty() || !getInvalidCharacters().contains("" + convert(c, index))) && (callback != null ? callback.call(convert(c, index)) : maskChar == convert(c, index)); } else return false; } @Override public void replaceText(int start, int end, String text) { if (getInputMask() != null) { int index = 0; int count = end - start; int caret = getCaretPosition(); int caretStart = caret; // donĀ“t accept user input space if (text.equals(" ")) { return; } String existingText = getText(); String deletedText = existingText.substring(start, end); String newText = keepFixedText(deletedText); super.replaceText(start, end, newText); while (start < getInputMask().length()) { if (index >= text.length()) break; char c = text.charAt(index); // get the char from the text. if (verifyChar(c, start)) { if (getText().length() > start) { // in case it is at the end of the text super.replaceText(start, start + 1, "" + convert(c, start)); } else { super.replaceText(start, start, "" + convert(c, start)); } index++; // move to the next char in the text start++; // move to the next caret position count--; // counting how many char we replaced and we just did one char here. } else { // auto-advance if (isAutoAdvance() && (getFixedText().contains("" + c) || (caret < existingText.length() && existingText.charAt(caret) != getPlaceholderCharacter() && getFixedText().contains("" + existingText.charAt(caret))))) { start++; // move to the next caret position caret++; selectRange(caret, caret); } else { break; } // Fix mask have space and caret is in this position String inputMask = getInputMask(); if ( inputMask != null ) { while ( Character.isSpaceChar(inputMask.charAt(start)) && (start < inputMask.length()) && (!text.isEmpty())) { start++; } this.replaceText(start, start, text); } } // only break when there is no new text and we are done with the count that we are expected to do // if there are still new text, we will continue even when the count is done if (count <= 0 && index >= text.length()) { break; } } // caret go to last position all times selectRange(start, start); // this code solution -> When user select all text and caret is in end line, text is lost if ( (caretStart == getInputMask().length()) && (start == 0) && (!text.isEmpty()) && (caretStart == caret) && (caretStart == end) ) { this.replaceText(start, start, text); } } else { super.replaceText(start, end, text); } } @Override public void replaceSelection(String replacement) { IndexRange range = getSelection(); if (getInputMask() != null) { String newText = keepFixedText(getSelectedText()); super.replaceText(range.getStart(), range.getEnd(), newText); selectRange(range.getStart(), range.getStart()); for (int i = 0; i < replacement.length(); i++) { int start = getCaretPosition(); replaceText(start, start, "" + replacement.charAt(i)); } } else { super.replaceSelection(replacement); } } // 5/10/2013 decided to remove this example as MaskFormatter is a Swing class. Don't want to introduce any unnecessary dependency on Swing // /** // * Configure the MaskTextField based on the information on the MaskFormatter. If you already have a MaskFormatter, // * you can use this method to automatically configure the MaskTextField. The items we will read from the // * MaskFormatter and set to the MaskTextField are InputMask, ConversionMask, Validcharacters, InvalidCharacters, and // * PlceholderCharacter. // * <p> // * Please note, in the current release, the escape char "'" that could be used by the MaskFormatter is not // * supported. // * // * @param formatter the MaskFormatter. // */ // public void fromMaskFormatter(MaskFormatter formatter) { // String mask = formatter.getMask(); // String inputMask = mask; // inputMask = inputMask.replace('#', '9') // .replace('A', 'N') // .replace('U', 'A') // .replace('L', 'A') // .replace('?', 'A') // .replace('*', 'X') //// .replace('H', 'H') // same // ; // setInputMask(inputMask); // String conversionMask = mask; // conversionMask = conversionMask.replace('#', '_') // .replace('A', '_') //// .replace('U', 'U') // same //// .replace('L', 'L') // same // .replace('?', '_') // .replace('*', '_') // .replace('H', '_') // ; // setConversionMask(conversionMask); // setValidCharacters(formatter.getValidCharacters()); // setInvalidCharacters(formatter.getInvalidCharacters()); // setPlaceholderCharacter(formatter.getPlaceholderCharacter()); // } /** * A static method which will create a MaskTextField for SSN (social security number, a 9-digit id number used in * United States for tax purposes). The InputMask for it is "999-99-9999". The ConversionMask and the RequiredMask * are not set which means no conversion and all positions are required. * * @return a MaskTextField for SSN. */ public static MaskTextField createSSNField() { MaskTextField field = new MaskTextField(); field.setInputMask("999-99-9999"); field.setPlaceholderCharacter('#'); return field; } /** * A static method which will create a MaskTextField for Phone Number in US and Canada. The InputMask for it is * "(999) 999-9999". The ConversionMask and the RequiredMask are not set which means no conversion and all positions * are required. * * @return a MaskTextField for a Phone Number in US and Canada. */ public static MaskTextField createPhoneNumberField() { MaskTextField field = new MaskTextField(); field.setInputMask("(999) 999-9999"); return field; } /** * A static method which will create a MaskTextField for the Zip Code in US. The InputMask for it is "99999". The * ConversionMask and the RequiredMask are not set which means no conversion and all positions are required. * * @return a MaskTextField for a Phone Number in US and Canada. */ public static MaskTextField createZipCodeField() { MaskTextField field = new MaskTextField(); field.setInputMask("99999"); return field; } /** * A static method which will create a MaskTextField for the Zip Code + 4 in US. The InputMask for it is * "99999-9999". The ConversionMask and the RequiredMask are not set which means no conversion and all positions are * required. * * @return a MaskTextField for a Phone Number in US and Canada. */ public static MaskTextField createZipCodePlus4Field() { MaskTextField field = new MaskTextField(); field.setInputMask("99999-9999"); return field; } /** * A static method which will create a MaskTextField for the Zip Code + 4 in US. The InputMask for it is * "99999-9999". The ConversionMask and the RequiredMask are not set which means no conversion and all positions are * required. * * @return a MaskTextField for a Phone Number in US and Canada. */ public static MaskTextField createCanadaPostalCodeField() { MaskTextField field = new MaskTextField(); field.setInputMask("A9A 9A9"); //NON-NLS field.setConversionMask("U_U _U_"); //NON-NLS field.setPlaceholderCharacter('_'); field.setAutoAdvance(false); return field; } /** * A static method which will create a MaskTextField for serial number. This particular one is commonly used by * Microsoft's recent products. The InputMask for it is "NNNNN-NNNNN-NNNNN-NNNNN-NNNNN". The ConversionMask is * "UUUUU_UUUUU_UUUUU_UUUUU_UUUUU". The RequiredMask are not set which means all positions are required. * * @return a MaskTextField for a serial number. */ @SuppressWarnings("SpellCheckingInspection") public static MaskTextField createSerialNumberField() { MaskTextField field = new MaskTextField(); field.setInputMask("NNNNN-NNNNN-NNNNN-NNNNN-NNNNN"); //NON-NLS field.setConversionMask("UUUUU_UUUUU_UUUUU_UUUUU_UUUUU"); //NON-NLS return field; } /** * A static method which will create a MaskTextField for IPv6 address. The InputMask for it is * "HHHH:HHHH:HHHH:HHHH:HHHH:HHHH:HHHH:HHHH". The ConversionMask is "LLLL_LLLL_LLLL_LLLL_LLLL_LLLL_LLLL_LLLL" so * that only lower case letters will be used, although according to the standard, the case shouldn't matter. The * RequiredMask are not set which means all positions are required. * * @return a MaskTextField for IPv6 address. */ @SuppressWarnings("SpellCheckingInspection") public static MaskTextField createIPv6Field() { MaskTextField field = new MaskTextField(); field.setInputMask("HHHH:HHHH:HHHH:HHHH:HHHH:HHHH:HHHH:HHHH"); //NON-NLS field.setConversionMask("LLLL_LLLL_LLLL_LLLL_LLLL_LLLL_LLLL_LLLL"); //NON-NLS return field; } /** * A static method which will create a MaskTextField for MAC address. The InputMask for it is "HH:HH:HH:HH:HH:HH". * The ConversionMask is "LL:LL:LL:LL:LL:LL" so that only lower case letters will be used, although according to the * standard, the case shouldn't matter. The RequiredMask are not set which means all positions are required. * * @return a MaskTextField for MAC address. */ public static MaskTextField createMacAddressField() { MaskTextField field = new MaskTextField(); field.setInputMask("HH:HH:HH:HH:HH:HH"); //NON-NLS field.setConversionMask("LL:LL:LL:LL:LL:LL"); //NON-NLS return field; } /** * A static method which will create a MaskTextField for the Date in US standard format (in the order of * month/day/year). The InputMask for it is "19/39/9999". The ConversionMask is "_x/_y/____" and we defined * conversion callbacks for both 'x' and 'y' to make sure the month is between 1 and 12, the day of the month is * between 1 and 31. * <p> * The initial text was set to the today's date. * * @return a MaskTextField for Date. */ public static MaskTextField createDateField() { MaskTextField field = new MaskTextField(); field.setInputMask("19/39/9999"); field.setConversionMask("_x/_y/____"); //NON-NLS field.getConversions().put('x', new Callback<Character, Character>() { @Override public Character call(Character param) { char c = field.getText().charAt(0); if (c == '1' && param > '2') { return '2'; } return param; } }); field.getConversions().put('y', new Callback<Character, Character>() { @Override public Character call(Character param) { char c = field.getText().charAt(3); if (c == '3' && param > '1') { return '1'; } return param; } }); field.setInitialText(new SimpleDateFormat("MM/dd/yyyy").format(Calendar.getInstance().getTime())); return field; } /** * A static method which will create a MaskTextField for the Time in the 12-hour format (in the order of * hour/minute/second AM/PM). The InputMask for it is "19:59:59 xm". The InputMask 'x' and 'm' were defined to make * sure the entered letter is 'A' or 'P', and 'M' respectively. The ConversionMask is "_x:__:__ UU" and we defined * conversion callbacks for the conversion mask 'x' to make sure the hour is between 1 and 12. * <p> * The initial text was set to the current time. * * @return a MaskTextField for Date. */ public static MaskTextField createTime12Field() { MaskTextField field = new MaskTextField(); field.setInputMask("19:59:59 xm"); //NON-NLS field.getInputMaskVerifiers().put('x', new Callback<Character, Boolean>() { @Override public Boolean call(Character c) { return c == 'A' || c == 'P'; } }); field.getInputMaskVerifiers().put('m', new Callback<Character, Boolean>() { @Override public Boolean call(Character c) { return c == 'M'; } }); field.setConversionMask("_x:__:__ UU"); //NON-NLS field.getConversions().put('x', new Callback<Character, Character>() { @Override public Character call(Character c) { char firstChar = field.getText().charAt(0); if (firstChar == '1' && c > '2') { return '2'; } if (firstChar == '0' && c == '0') { return '1'; } return c; } }); field.setInitialText(new SimpleDateFormat("hh:mm:ss aa").format(Calendar.getInstance().getTime())); return field; } /** * A static method which will create a MaskTextField for the Time in the 24-hour format (in the order of * hour/minute/second). The InputMask for it is "29/59/59". The ConversionMask is "_x/__/__" and we defined * conversion callbacks for the conversion mask named 'x' to make sure the hour is between 0 and 23. * <p> * The initial text was set to the current time. * * @return a MaskTextField for Date. */ public static MaskTextField createTime24Field() { MaskTextField field = new MaskTextField(); field.setInputMask("29:59:59"); field.setConversionMask("_x:__:__"); //NON-NLS field.getConversions().put('x', new Callback<Character, Character>() { @Override public Character call(Character c) { char firstChar = field.getText().charAt(0); if (firstChar == '2' && c > '3') { return '3'; } return c; } }); field.setInitialText(new SimpleDateFormat("HH:mm:ss").format(Calendar.getInstance().getTime())); return field; } private void showClearButton() { if (_clearButtonDecorator == null) { _clearButtonDecorator = PredefinedDecorators.getInstance().getClearButtonDecoratorSupplier().get(); _clearButtonDecorator.getNode().disableProperty().bind(disabledProperty()); _clearButtonDecorator.getNode().setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { if (!isDisabled()) clear(); } }); } DecorationUtils.install(this, _clearButtonDecorator); } private void hideClearButton() { if (_clearButtonDecorator != null) { DecorationUtils.uninstall(this, _clearButtonDecorator); } } public BooleanProperty clearButtonVisibleProperty() { if (_clearbuttonVisibleProperty == null) { _clearbuttonVisibleProperty = new SimpleBooleanProperty(this, "clearButtonVisible") { //NON-NLS @Override protected void invalidated() { super.invalidated(); if (get()) { showClearButton(); } else { hideClearButton(); } } }; } return _clearbuttonVisibleProperty; } /** * Checsk if the clear button is visible. The clear button will clear the text on the field. * * @return true or false. */ public boolean isClearButtonVisible() { return clearButtonVisibleProperty().get(); } /** * Shows or hides the clear button. * * @param clearButtonVisible true or false. */ public void setClearButtonVisible(boolean clearButtonVisible) { clearButtonVisibleProperty().set(clearButtonVisible); } // For decoration @Override protected void layoutChildren() { prepareDecorations(); super.layoutChildren(); Platform.runLater(this::layoutDecorations); } private DecorationDelegate _operator; public void prepareDecorations() { if (_operator == null) { _operator = new DecorationDelegate(this); } _operator.prepareDecorations(); } public void layoutDecorations() { _operator.layoutDecorations(); } // For interface DecorationSupport @Override public ObservableList<Node> getChildren() { return super.getChildren(); } @Override public void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight, double areaBaselineOffset, HPos halignment, VPos valignment) { super.positionInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, halignment, valignment); } // end decoration }