com.haulmont.cuba.web.toolkit.ui.client.textfield.CubaMaskedFieldWidget.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.web.toolkit.ui.client.textfield.CubaMaskedFieldWidget.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.haulmont.cuba.web.toolkit.ui.client.textfield;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.*;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.ui.VTextField;
import com.vaadin.shared.EventId;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class CubaMaskedFieldWidget extends VTextField {

    public static final String CLASSNAME = "c-maskedfield";

    protected static final String EMPTY_FIELD_CLASS = "c-maskedfield-empty";

    protected static final char PLACE_HOLDER = '_';

    protected StringBuilder valueBuilder;

    protected String nullRepresentation;

    protected String mask;

    protected List<Mask> maskTest;

    protected Map<Character, Mask> maskMap = new HashMap<>();

    protected boolean maskedMode = false;

    protected boolean sendNullRepresentation = true;

    protected MaskedKeyHandler keyHandler;

    protected boolean focused = false;

    protected boolean shiftPressed = false;
    protected int shiftPressPos = -1;

    protected boolean isTimeMask = false;

    public CubaMaskedFieldWidget() {
        setStylePrimaryName(CLASSNAME);
        setStyleName(CLASSNAME);
        valueBeforeEdit = "";

        initMaskMap();

        keyHandler = new MaskedKeyHandler();
        addKeyPressHandler(keyHandler);
        addKeyDownHandler(keyHandler);
        addKeyUpHandler(keyHandler);

        addInputHandler(getElement());
    }

    @Override
    public void onFocus(FocusEvent event) {
        super.onFocus(event);

        if (!this.focused) {
            this.focused = true;

            if (!isReadOnly() && isEnabled()) {
                if (mask != null && nullRepresentation != null && nullRepresentation.equals(super.getText())) {
                    addStyleName("c-focus-move");

                    Scheduler.get().scheduleDeferred(() -> {
                        if (!isReadOnly() && isEnabled() && focused) {
                            setSelectionRange(getPreviousPos(0), 0);
                        }

                        removeStyleName("c-focus-move");
                    });
                }
            }
        }
    }

    @Override
    public void onBlur(BlurEvent event) {
        super.onBlur(event);

        this.shiftPressed = false;
        this.shiftPressPos = -1;
        this.focused = false;
    }

    public boolean isMaskedMode() {
        return maskedMode;
    }

    public void setMaskedMode(boolean maskedMode) {
        this.maskedMode = maskedMode;
    }

    public void setTimeMask(boolean isTimeMask) {
        this.isTimeMask = isTimeMask;
    }

    public boolean isSendNullRepresentation() {
        return sendNullRepresentation;
    }

    public void setSendNullRepresentation(boolean sendNullRepresentation) {
        this.sendNullRepresentation = sendNullRepresentation;
    }

    protected void initMaskMap() {
        maskMap.put('#', new NumericMask());
        maskMap.put('U', new UpperCaseMask());
        maskMap.put('L', new LowerCaseMask());
        maskMap.put('?', new LetterMask());
        maskMap.put('A', new AlphanumericMask());
        maskMap.put('*', new WildcardMask());
        maskMap.put('H', new UpperCaseHexMask());
        maskMap.put('h', new LowerCaseHexMask());
        maskMap.put('~', new SignMask());
    }

    protected void updateCursor(int pos) {
        setCursorPos(getNextPos(pos));
    }

    protected int getNextPos(int pos) {
        while (++pos < maskTest.size() && maskTest.get(pos) == null) {
        }
        if (pos >= maskTest.size())
            return getPreviousPos(maskTest.size()) + 1;
        return pos;
    }

    int getPreviousPos(int pos) {
        while (--pos >= 0 && maskTest.get(pos) == null) {
        }
        if (pos < 0)
            return getNextPos(pos);
        return pos;
    }

    @Override
    public void setText(String value) {
        valueBuilder = maskValue(value);
        if (valueBuilder.toString().equals(nullRepresentation) || valueBuilder.length() == 0) {
            getElement().addClassName(EMPTY_FIELD_CLASS);
        } else {
            getElement().removeClassName(EMPTY_FIELD_CLASS);
        }
        super.setText(valueBuilder.toString());
    }

    public String getRawText() {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < maskTest.size(); i++) {
            Mask mask = maskTest.get(i);
            if (mask != null) {
                if (valueBuilder.charAt(i) != PLACE_HOLDER) {
                    result.append(valueBuilder.charAt(i));
                } else
                    result.append("");
            }
        }
        return result.toString();
    }

    protected StringBuilder maskValue(String value) {
        if (maskTest == null) {
            return new StringBuilder(value);
        } else {
            return maskValue(value, 0, maskTest.size());
        }
    }

    protected StringBuilder maskValue(String value, int start, int end) {
        StringBuilder result = new StringBuilder();
        if (maskTest == null) {
            return result.append(value);
        }
        if (value.equals(nullRepresentation.substring(start, end))) {
            result.append(value);
            return result;
        }
        int valuePos = 0;
        for (int i = start; i < maskTest.size() && valuePos < end - start; i++) {
            if (valuePos >= value.length()) {
                result.append(nullRepresentation.charAt(i));
                valuePos++;
            } else {
                Mask mask = maskTest.get(i);
                if (mask == null) {
                    if (nullRepresentation.charAt(i) == value.charAt(valuePos)) {
                        result.append(value.charAt(valuePos));
                        valuePos++;
                    } else {
                        result.append(nullRepresentation.charAt(i));
                    }
                } else {
                    if (mask.isValid(value.charAt(valuePos))) {
                        result.append(mask.getChar(value.charAt(valuePos)));
                        valuePos++;
                    } else {
                        result.append(nullRepresentation.charAt(i));
                        valuePos++;
                    }
                }
            }
        }
        return result;
    }

    public void setMask(String mask) {
        if (mask.equals(this.mask)) {
            return;
        }

        this.mask = mask;
        valueBuilder = new StringBuilder();
        maskTest = new ArrayList<>();

        for (int i = 0; i < mask.length(); i++) {
            char c = mask.charAt(i);
            if (c == '\'') {
                maskTest.add(null);
                valueBuilder.append(mask.charAt(++i));
            } else if (maskMap.get(c) != null) {
                maskTest.add(maskMap.get(c));
                valueBuilder.append(PLACE_HOLDER);
            } else {
                maskTest.add(null);
                valueBuilder.append(c);
            }
        }
        nullRepresentation = valueBuilder.toString();
        setText(valueBuilder.toString());
    }

    protected boolean validateText(String text) {
        if (text.equals(nullRepresentation)) {
            return true;
        }
        for (int i = 0; i < maskTest.size(); i++) {
            Mask mask = maskTest.get(i);
            if (mask != null) {
                if (!mask.isValid(text.charAt(i))) {
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public void valueChange(boolean blurred) {
        if (client != null && paintableId != null) {

            boolean sendBlurEvent = false;
            boolean sendValueChange = false;

            if (blurred && client.hasEventListeners(this, EventId.BLUR)) {
                sendBlurEvent = true;
                client.updateVariable(paintableId, EventId.BLUR, "", false);
            }

            String newText = getText();

            if (!prompting && newText != null && !newText.equals(valueBeforeEdit)) {
                if (isTimeMask && newText.endsWith("__") && !newText.startsWith("__")) {
                    newText = newText.replaceAll("__", "00");
                }
                if (validateText(newText)) {
                    sendValueChange = immediate;
                    String value;
                    if (newText.equals(nullRepresentation)) {
                        value = isSendNullRepresentation() ? getText() : getRawText();
                    } else {
                        if (maskedMode) {
                            if (isTimeMask) {
                                value = newText;
                            } else {
                                value = getText();
                            }
                        } else {
                            value = getRawText();
                        }
                    }
                    client.updateVariable(paintableId, "text", value, false);
                    valueBeforeEdit = newText;
                } else {
                    setText(valueBeforeEdit);
                }
            }

            if (sendBlurEvent || sendValueChange) {
                client.sendPendingVariableChanges();
            }
        }
    }

    public void updateTextState() {
        if (valueBeforeEdit == null || !getText().equals(valueBeforeEdit)) {
            valueBeforeEdit = getText();
        }
    }

    protected native void addInputHandler(Element elementID)/*-{
                                                            var temp = this;  // hack to hold on to 'this' reference
                                                                
                                                            var listener = $entry(function (e) {
                                                            temp.@com.haulmont.cuba.web.toolkit.ui.client.textfield.CubaMaskedFieldWidget::handleInput(*)(e.inputType);
                                                            });
                                                                
                                                            if (elementID.addEventListener) {
                                                            elementID.addEventListener("input", listener, false);
                                                            } else {
                                                            elementID.attachEvent("input", listener);
                                                            }
                                                            }-*/;

    public void handleInput(String inputType) {
        String newText = getText();
        if (inputType != null) {
            switch (inputType) {
            case "deleteByCut":
                handleCut(newText);
                break;
            case "insertFromPaste":
                handlePaste(newText);
                break;
            default:
                Logger.getLogger("CubaMaskedFieldWidget").log(Level.WARNING, "Unknown inputType");
            }
        } else {
            if (newText.length() < valueBuilder.length()) {
                handleCut(newText);
            } else {
                handlePaste(newText);
            }
        }
    }

    protected void handleCut(String newText) {
        int cursorPos = getCursorPos();
        if (cursorPos == 0 && newText.length() == 0) {
            valueBuilder.replace(0, valueBuilder.length(), nullRepresentation);
            super.setText(nullRepresentation);
            setCursorPos(getPreviousPos(0));
        } else {
            int cutLength = valueBuilder.length() - newText.length();

            StringBuilder resultValue = new StringBuilder(valueBuilder.substring(0, cursorPos));
            resultValue.append(nullRepresentation.substring(cursorPos, cursorPos + cutLength));
            resultValue.append(valueBuilder.substring(cursorPos + cutLength));

            valueBuilder.replace(0, valueBuilder.length(), resultValue.toString());
            super.setText(resultValue.toString());
            setCursorPos(cursorPos);
        }
    }

    protected void handlePaste(String newText) {
        int pasteLength = newText.length() - valueBuilder.length();
        int pasteStart = getCursorPos() - pasteLength;

        if (newText.length() == valueBuilder.length()) {
            pasteStart = 0;
            pasteLength = valueBuilder.length();
        }

        StringBuilder maskedPart = maskValue(newText.substring(pasteStart, pasteStart + pasteLength), pasteStart,
                pasteStart + pasteLength);
        valueBuilder.replace(pasteStart, pasteStart + maskedPart.length(), maskedPart.toString());
        super.setText(valueBuilder.toString());
        setCursorPos(getNextPos(pasteStart + maskedPart.length() - 1));
    }

    protected void setRawCursorPosition(int pos) {
        if (pos >= 0 && pos <= maskTest.size())
            setCursorPos(pos);
    }

    public interface Mask {
        boolean isValid(char c);

        char getChar(char c);
    }

    public static abstract class AbstractMask implements Mask {
        @Override
        public char getChar(char c) {
            return c;
        }
    }

    public static class NumericMask extends AbstractMask {
        @Override
        public boolean isValid(char c) {
            return Character.isDigit(c);
        }
    }

    public static class LetterMask extends AbstractMask {
        @Override
        public boolean isValid(char c) {
            //it's not  proper way to recognize letters
            return (Character.toLowerCase(c) != Character.toUpperCase(c));
        }
    }

    /**
     * Represents a hex character, 0-9a-fA-F. a-f is mapped to A-F
     */
    public static class HexMask extends AbstractMask {
        @Override
        public boolean isValid(char c) {
            return ((c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7'
                    || c == '8' || c == '9' || c == 'a' || c == 'A' || c == 'b' || c == 'B' || c == 'c' || c == 'C'
                    || c == 'd' || c == 'D' || c == 'e' || c == 'E' || c == 'f' || c == 'F'));
        }
    }

    public static class LowerCaseHexMask extends HexMask {
        @Override
        public char getChar(char c) {
            if (Character.isDigit(c)) {
                return c;
            }
            return Character.toLowerCase(c);
        }
    }

    public static class UpperCaseHexMask extends HexMask {
        @Override
        public char getChar(char c) {
            if (Character.isDigit(c)) {
                return c;
            }
            return Character.toUpperCase(c);
        }
    }

    public static class LowerCaseMask extends LetterMask {
        @Override
        public boolean isValid(char c) {
            return (Character.toLowerCase(c) != Character.toUpperCase(c));
        }

        @Override
        public char getChar(char c) {
            return Character.toLowerCase(c);
        }
    }

    public static class UpperCaseMask extends LetterMask {
        @Override
        public boolean isValid(char c) {
            return (Character.toLowerCase(c) != Character.toUpperCase(c));
        }

        @Override
        public char getChar(char c) {
            return Character.toUpperCase(c);
        }
    }

    public static class AlphanumericMask extends AbstractMask {
        @Override
        public boolean isValid(char c) {
            return (Character.isDigit(c) || (Character.toLowerCase(c) != Character.toUpperCase(c)));
        }
    }

    public static class WildcardMask extends AbstractMask {
        @Override
        public boolean isValid(char c) {
            return true;
        }
    }

    public static class SignMask extends AbstractMask {
        @Override
        public boolean isValid(char c) {
            return c == '-' || c == '+';
        }
    }

    public class MaskedKeyHandler implements KeyDownHandler, KeyUpHandler, KeyPressHandler {

        protected int getCursorPosSelection() {
            return getCursorPos() + getSelectionLength();
        }

        @Override
        public void onKeyDown(KeyDownEvent event) {
            if (event.getNativeKeyCode() == KeyCodes.KEY_SHIFT && !shiftPressed) {
                shiftPressed = true;
                shiftPressPos = getCursorPos();
            }
            if (isReadOnly())
                return;
            if (event.getNativeKeyCode() == KeyCodes.KEY_BACKSPACE) {
                int beginPos = getCursorPos();
                int endPos = getCursorPosSelection();
                if (getSelectionLength() == 0) {
                    beginPos = getPreviousPos(beginPos);
                }

                removeCharsAtRange(beginPos, endPos);

                setCursorToInputPosition(beginPos);
                event.preventDefault();
            } else if (event.getNativeKeyCode() == KeyCodes.KEY_DELETE) {
                int beginPos = getCursorPos();
                int endPos = getCursorPosSelection();
                boolean isHasSelection = getSelectionLength() > 0;
                if (!isHasSelection) {
                    endPos = getNextPos(beginPos);
                }

                removeCharsAtRange(beginPos, endPos);

                if (isHasSelection) {
                    setCursorToInputPosition(beginPos);
                } else {
                    updateCursor(beginPos);
                }
                event.preventDefault();
            } else if (event.getNativeKeyCode() == KeyCodes.KEY_RIGHT) {
                if (getCursorPosSelection() <= shiftPressPos) {
                    setCursorPos(getNextPos(getCursorPos()));
                } else {
                    setCursorPos(getNextPos(getCursorPosSelection()));
                }
                updateSelectionRange();
                event.preventDefault();
            } else if (event.getNativeKeyCode() == KeyCodes.KEY_LEFT) {
                if (getCursorPosSelection() <= shiftPressPos) {
                    setCursorPos(getPreviousPos(getCursorPos()));
                } else {
                    setCursorPos(getPreviousPos(getCursorPosSelection()));
                }
                updateSelectionRange();
                event.preventDefault();
            } else if (event.getNativeKeyCode() == KeyCodes.KEY_HOME
                    || event.getNativeKeyCode() == KeyCodes.KEY_UP) {
                setCursorPos(getPreviousPos(0));
                updateSelectionRange();
                event.preventDefault();
            } else if (event.getNativeKeyCode() == KeyCodes.KEY_END
                    || event.getNativeKeyCode() == KeyCodes.KEY_DOWN) {
                setCursorPos(getPreviousPos(getValue().length()) + 1);
                updateSelectionRange();
                event.preventDefault();
            }
        }

        protected void removeCharsAtRange(int from, int to) {
            for (int pos = from; pos < to; pos++) {
                if (pos < maskTest.size()) {
                    Mask m = maskTest.get(pos);
                    if (m != null) {
                        valueBuilder.setCharAt(pos, PLACE_HOLDER);
                    }
                }
            }
            setValue(valueBuilder.toString());
        }

        protected void setCursorToInputPosition(int pos) {
            if (pos >= 0 && pos < maskTest.size()) {
                Mask m = maskTest.get(pos);
                if (m == null) {
                    pos = getNextPos(pos);
                }
            }
            setCursorPos(pos);
        }

        protected void updateSelectionRange() {
            if (shiftPressed) {
                if (getCursorPosSelection() > shiftPressPos) {
                    setSelectionRange(shiftPressPos, getCursorPosSelection() - shiftPressPos);
                } else {
                    setSelectionRange(getCursorPosSelection(), shiftPressPos - getCursorPosSelection());
                }
            }
        }

        @Override
        public void onKeyUp(KeyUpEvent event) {
            if (event.getNativeKeyCode() == KeyCodes.KEY_SHIFT) {
                shiftPressed = false;
                shiftPressPos = -1;
            }
        }

        @Override
        public void onKeyPress(KeyPressEvent e) {
            if (isReadOnly())
                return;

            if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER && !e.getNativeEvent().getAltKey()
                    && !e.getNativeEvent().getCtrlKey() && !e.getNativeEvent().getShiftKey()) {
                valueChange(false);
            }

            if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_BACKSPACE
                    || e.getNativeEvent().getKeyCode() == KeyCodes.KEY_DELETE
                    || e.getNativeEvent().getKeyCode() == KeyCodes.KEY_END
                    || e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER
                    || e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE
                    || e.getNativeEvent().getKeyCode() == KeyCodes.KEY_HOME
                    || e.getNativeEvent().getKeyCode() == KeyCodes.KEY_LEFT
                    || e.getNativeEvent().getKeyCode() == KeyCodes.KEY_PAGEDOWN
                    || e.getNativeEvent().getKeyCode() == KeyCodes.KEY_PAGEUP
                    || e.getNativeEvent().getKeyCode() == KeyCodes.KEY_RIGHT || e.getNativeEvent().getAltKey()
            //|| e.getNativeEvent().getCtrlKey()
            //|| e.getNativeEvent().getMetaKey()
            ) {

                e.preventDefault(); // KK: otherwise incorrectly handles combinations like Shift+'='
                return;
            } else if (BrowserInfo.get().isGecko() && e.getCharCode() == '\u0000' || e.getNativeEvent().getCtrlKey()
                    || e.getNativeEvent().getMetaKey()) {
                //pressed tab in firefox or ctrl. because FF fires keyPressEvents on CTRL+C
                // MetaKey for Mac OS X for Cmd + key commands
                return;
            }
            if (getCursorPos() < maskTest.size()) {
                Mask m = maskTest.get(getCursorPos());
                if (m != null) {
                    if (m.isValid(e.getCharCode()) && e.getCharCode() != PLACE_HOLDER) {
                        int pos = getCursorPos();
                        valueBuilder.setCharAt(pos, m.getChar(e.getCharCode()));
                        setValue(valueBuilder.toString());
                        updateCursor(pos);
                    }
                } else {
                    updateCursor(getCursorPos());
                }
            }
            e.preventDefault();
        }
    }
}