/* * Copyright (c) 2011 PonySDK * Owners: * Luciano Broussal <luciano.broussal AT gmail.com> * Mathieu Barbier <mathieu.barbier AT gmail.com> * Nicolas Ciaravola <nicolas.ciaravola.pro AT gmail.com> * * WebSite: * http://code.google.com/p/pony-sdk/ * * 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.ponysdk.core.terminal.ui.widget.mask; import com.google.gwt.core.client.Scheduler; import com.google.gwt.event.dom.client.*; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.user.client.ui.TextBox; import java.util.ArrayList; import java.util.List; public class TextBoxMaskedDecorator implements KeyPressHandler, FocusHandler, KeyDownHandler { private static final RegExp regExp = RegExp.compile("{{([A0]+)}}", "g"); private final InputValue value = new InputValue(); private final TextBox textBox; public TextBoxMaskedDecorator(final TextBox textBox) { this.textBox = textBox; this.textBox.addKeyDownHandler(this); this.textBox.addKeyPressHandler(this); this.textBox.addFocusHandler(this); } public void setMask(final String mask, final boolean showMask, final char freeSymbol) { final List<Flag> flags = new ArrayList<>(); int current = 0; MatchResult matchResult = regExp.exec(mask); while (matchResult != null) { final String group = matchResult.getGroup(1); // Fill forced for (int i = current; i < matchResult.getIndex(); i++) { final char c = mask.charAt(current); flags.add(new Const(c, showMask, freeSymbol)); current++; } // start of group '{{' current += 2; for (int i = 0; i < group.length(); i++) { if (Character.isDigit(group.charAt(i))) flags.add(new Digit(freeSymbol)); else flags.add(new Char(freeSymbol)); current++; } // end of group '}}' current += 2; matchResult = regExp.exec(mask); } // Fill forced for (int i = current; i < mask.length(); i++) { final char c = mask.charAt(current); flags.add(new Const(c, showMask, freeSymbol)); current++; } value.init(flags); final String currentText = getText(); for (int c = 0; c < currentText.length(); c++) { final int insert = value.insert(c, currentText.charAt(c)); if (insert == -1) break; } final String text = value.getText(); setText(text); setMaxLength(text.length()); } @Override public void onFocus(final FocusEvent event) { Scheduler.get().scheduleDeferred(this::setInitialPosition); } protected void setInitialPosition() { setText(value.getText()); setCursorPos(value.getInsertPosition()); } private void refresh(final int newPosition) { setText(value.getText()); setCursorPos(newPosition); } @Override public void onKeyPress(final KeyPressEvent event) { final int pos = getCursorPos(); if (getSelectionLength() > 0) clearSelection(); final int nextPos = value.insert(pos, event.getCharCode()); if (nextPos != -1) { refresh(nextPos); } cancelKey(); } @Override public void onKeyDown(final KeyDownEvent event) { final int pos = getCursorPos(); if (event.getNativeKeyCode() == KeyCodes.KEY_BACKSPACE) { if (getSelectionLength() > 0) { clearSelection(); cancelKey(); return; } final int from = pos - 1; final int to = from + 1; int np = pos - 1; if (np < 0) np = 0; if (value.remove(from, to)) { refresh(np); } else { setCursorPos(np); } cancelKey(); } else if (event.getNativeKeyCode() == KeyCodes.KEY_DELETE) { if (getSelectionLength() > 0) { clearSelection(); cancelKey(); return; } final int to = pos + 1; if (value.remove(pos, to)) { refresh(pos); } else { setCursorPos(pos); } cancelKey(); } } private void clearSelection() { final int from = getCursorPos(); final int to = from + getSelectionLength(); if (value.remove(from, to)) { refresh(from); } else { setCursorPos(from); } } // Delegates public void cancelKey() { textBox.cancelKey(); } public int getCursorPos() { return textBox.getCursorPos(); } public void setCursorPos(final int pos) { textBox.setCursorPos(pos); } public String getText() { return textBox.getText(); } public void setText(final String text) { textBox.setText(text); } public int getSelectionLength() { return textBox.getSelectionLength(); } public void setMaxLength(final int length) { textBox.setMaxLength(length); } public static abstract class Flag { protected boolean set = false; protected char c; protected char freeSymbol; protected boolean showMask; public Flag() { } public Flag(final char c, final boolean showMask) { this.c = c; this.set = true; this.showMask = showMask; } public abstract boolean match(final char c); public boolean isDigit() { return false; } public boolean isChar() { return false; } public boolean isConst() { return false; } public boolean isSet() { return set; } public void unset() { this.set = false; } public void set(final char c) { this.c = c; this.set = true; } @Override public String toString() { if (isSet()) return Character.toString(c); return " "; } public char getValue(final boolean filling) { if (set) return c; return freeSymbol; } } public static class Digit extends Flag { public Digit(final char freeSymbol) { super(); this.freeSymbol = freeSymbol; } @Override public boolean isDigit() { return true; } @Override public boolean match(final char c) { return Character.isDigit(c); } @Override public String toString() { if (isSet()) return Character.toString(c); return "0"; } } public static class Char extends Flag { public Char(final char freeSymbol) { super(); this.freeSymbol = freeSymbol; } @Override public boolean isChar() { return true; } @Override public boolean match(final char c) { return Character.isLetter(c); } @Override public String toString() { if (isSet()) return Character.toString(c); return "@"; } } public static class Const extends Flag { public Const(final char c, final boolean showMask, final char freeSymbol) { super(c, showMask); this.freeSymbol = freeSymbol; } @Override public boolean isConst() { return true; } @Override public boolean match(final char c) { return c == this.c; } @Override public char getValue(final boolean filling) { if (!filling) { if (showMask) return c; return freeSymbol; } if (set) return c; return freeSymbol; } } public static class InputValue { private int insertPosition = 0; private List<Flag> flags; public void init(final List<Flag> flags) { this.flags = flags; } public void reset() { for (final Flag f : flags) { if (!f.isConst()) f.unset(); } } public String getText() { boolean updateInsert = true; insertPosition = 0; int pos = 0; final StringBuilder sb = new StringBuilder(); for (final Flag f : flags) { sb.append(f.getValue(updateInsert)); if (!f.isSet()) { updateInsert = false; } pos++; if (updateInsert) insertPosition = pos; } return sb.toString(); } int getInsertPosition() { return insertPosition; } public int insert(final int pos, final char charCode) { // if full, return if (pos >= flags.size()) return -1; int nextPositon; Flag next = null; for (nextPositon = pos; nextPositon < flags.size(); nextPositon++) { next = flags.get(nextPositon); if (next.isConst()) { if (next.match(charCode)) { // consume key and exit return -1; } } else { break; } } if (next == null) return -1; // if not assignable, return if (!next.match(charCode)) return -1; // we can set the new value, shift if required if (!next.isSet()) { next.set(charCode); } else { insert(pos + 1, next.c); next.set(charCode); } return nextPositon + 1; } public boolean remove(final int from, final int to) { if (from < 0) return false; for (int f = from; f < to; f++) { final Flag flag = flags.get(f); if (!flag.isConst()) flag.unset(); } shift(from); return true; } private void shift(final int pos) { if (pos >= flags.size()) return; // go to next "settable" int next; Flag flag = null; for (next = pos; next < flags.size(); next++) { flag = flags.get(next); if (!flag.isConst() && !flag.isSet()) break; } if (flag == null) return; next++; // find next "value" for (int i = next; i < flags.size(); i++) { final Flag n = flags.get(i); if (n.isConst()) continue; if (!n.isSet()) continue; if (flag.match(n.c)) { flag.set(n.c); n.unset(); } break; } shift(next); } } }