/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* Copyright (c) 2013, MPL CodeInside http://codeinside.ru
*/
package ru.codeinside.gses.vaadin.client;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.Command;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.UIDL;
import com.vaadin.terminal.gwt.client.ui.VTextField;
import java.util.ArrayList;
import java.util.List;
import static com.google.gwt.event.dom.client.KeyCodes.KEY_BACKSPACE;
import static com.google.gwt.event.dom.client.KeyCodes.KEY_DELETE;
import static com.google.gwt.event.dom.client.KeyCodes.KEY_DOWN;
import static com.google.gwt.event.dom.client.KeyCodes.KEY_END;
import static com.google.gwt.event.dom.client.KeyCodes.KEY_HOME;
import static com.google.gwt.event.dom.client.KeyCodes.KEY_LEFT;
import static com.google.gwt.event.dom.client.KeyCodes.KEY_RIGHT;
import static com.google.gwt.event.dom.client.KeyCodes.KEY_UP;
public class VMaskedTextField extends VTextField {
public void onFocus(FocusEvent event) {
super.onFocus(event);
// для FF этого достаточно!
event.preventDefault();
// немедленное позиционирование не работает в хроме!
if (getValue().isEmpty()) {
setValue(blank);
Scheduler.get().scheduleDeferred(new Command() {
public void execute() {
updateCursor(-1);
}
});
} else {
final int pos = string.indexOf("_");
if (pos >= 0) {
Scheduler.get().scheduleDeferred(new Command() {
public void execute() {
updateCursor(pos - 1);
}
});
}
}
}
public void onBlur(BlurEvent event) {
if (getValue().equals(blank)) {
setText("");
setValue("");
}
super.onBlur(event);
}
final static char PLACEHOLDER = '_';
List<Mask> maskTest = new ArrayList<Mask>();
StringBuilder string = new StringBuilder();
String blank = "";
public VMaskedTextField() {
addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(final KeyPressEvent e) {
final int key = e.getNativeEvent().getKeyCode();
final int char_ = e.getNativeEvent().getCharCode();
//VConsole.log("press " + key + ", char " + char_);
if (key == KEY_DELETE) {
e.preventDefault();
}
if (e.isAltKeyDown() || e.isControlKeyDown() || e.isMetaKeyDown() || char_ == 0) { // клавиши управления без символов!
return;
}
if (getCursorPos() < maskTest.size()) {
final int cursorPos = getCursorPos();
final Mask symbol = maskTest.get(cursorPos);
if (symbol != null) {
final char charCode = e.getCharCode();
if (symbol.isValid(charCode)) {
string.setCharAt(cursorPos, symbol.getChar(charCode));
setValue(string.toString());
updateCursor(cursorPos);
}
} else {
updateCursor(cursorPos);
}
}
e.preventDefault();
}
});
addKeyDownHandler(new KeyDownHandler() {
@Override
public void onKeyDown(KeyDownEvent event) {
final int keyCode = event.getNativeKeyCode();
final int charCode = event.getNativeEvent().getCharCode();
//VConsole.log("down " + keyCode + ", char " + charCode);
if (keyCode == KEY_BACKSPACE) {
int pos = getPreviousPos(getCursorPos());
Mask m = maskTest.get(pos);
if (m != null) {
string.setCharAt(pos, PLACEHOLDER);
setValue(string.toString());
}
setCursorPos(pos);
event.preventDefault();
} else if (keyCode == KEY_DELETE || (keyCode == 0 && charCode == 0)) {
final int pos = getCursorPos();
if (pos < maskTest.size()) {
final Mask m = maskTest.get(pos);
if (m != null) {
string.setCharAt(pos, PLACEHOLDER);
setValue(string.toString());
}
updateCursor(pos);
}
event.preventDefault();
} else if (keyCode == KEY_RIGHT) {
setCursorPos(getNextPos(getCursorPos()));
event.preventDefault();
} else if (keyCode == KEY_LEFT) {
setCursorPos(getPreviousPos(getCursorPos()));
event.preventDefault();
} else if (keyCode == KEY_HOME || keyCode == KEY_UP) {
setCursorPos(getPreviousPos(0));
event.preventDefault();
} else if (keyCode == KEY_END || keyCode == KEY_DOWN) {
setCursorPos(getPreviousPos(getValue().length()) + 1);
event.preventDefault();
}
}
});
}
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
if (uidl.hasAttribute("mask")) {
setMask(uidl.getStringAttribute("mask"));
}
super.updateFromUIDL(uidl, client);
}
//TODO: канонизация значения по маске!
public void setText(final String value) {
string = new StringBuilder(value);
super.setText(value);
}
private void setMask(final String mask) {
final StringBuilder sb = new StringBuilder();
maskTest = new ArrayList<Mask>();
if (mask != null) {
for (int i = 0; i < mask.length(); i++) {
char c = mask.charAt(i);
if (c == '\'') {
maskTest.add(null);
sb.append(mask.charAt(++i));
} else if (c == '#') {
maskTest.add(new NumericMask());
sb.append(PLACEHOLDER);
} else if (c == 'U') {
maskTest.add(new UpperCaseMask());
sb.append(PLACEHOLDER);
} else if (c == 'L') {
maskTest.add(new LowerCaseMask());
sb.append(PLACEHOLDER);
} else if (c == '?') {
maskTest.add(new LetterMask());
sb.append(PLACEHOLDER);
} else if (c == 'A') {
maskTest.add(new AlphanumericMask());
sb.append(PLACEHOLDER);
} else if (c == '*') {
maskTest.add(new WildcardMask());
sb.append(PLACEHOLDER);
} else if (c == 'H') {
maskTest.add(new HexMask());
sb.append(PLACEHOLDER);
} else if (c == '~') {
maskTest.add(new SignMask());
sb.append(PLACEHOLDER);
} else {
maskTest.add(null);
sb.append(c);
}
}
}
blank = sb.toString();
}
private void updateCursor(int pos) {
setCursorPos(getNextPos(pos));
}
private int getNextPos(int pos) {
while (++pos < maskTest.size() && maskTest.get(pos) == null) {
//
}
if (pos > maskTest.size()) {
pos = maskTest.size();
}
return pos;
}
int getPreviousPos(int pos) {
while (--pos >= 0 && maskTest.get(pos) == null) {
//
}
if (pos < 0)
return getNextPos(pos);
return pos;
}
static boolean isCyrillic(final char c) {
return (0x400 <= c && c <= 0x4ff) || //Cyrillic
(0x500 <= c && c <= 0x52f) || //Cyrillic Supplement
(0x2de0 <= c && c <= 0x2dff) || //Cyrillic Extended-A
(0xa640 <= c && c <= 0xa69f) || //Cyrillic Extended-B
(0x1d2b <= c && c <= 0x1d78); //Phonetic Extensions
}
abstract static class Mask {
abstract boolean isValid(char c);
char getChar(char c) {
return c;
}
}
final static class NumericMask extends Mask {
@Override
boolean isValid(char c) {
return Character.isDigit(c);
}
}
final static class LetterMask extends Mask {
@Override
boolean isValid(char c) {
return Character.isLetter(c) || isCyrillic(c);
}
}
final static class LowerCaseMask extends Mask {
@Override
boolean isValid(char c) {
return Character.isLetter(c) || isCyrillic(c);
}
@Override
char getChar(char c) {
return Character.toLowerCase(c);
}
}
final static class UpperCaseMask extends Mask {
@Override
boolean isValid(char c) {
return Character.isLetter(c) || isCyrillic(c);
}
@Override
char getChar(char c) {
return Character.toUpperCase(c);
}
}
final static class AlphanumericMask extends Mask {
@Override
boolean isValid(char c) {
return Character.isLetter(c) || Character.isDigit(c) || isCyrillic(c);
}
}
final static class WildcardMask extends Mask {
@Override
boolean isValid(char c) {
return true;
}
}
final static class SignMask extends Mask {
@Override
boolean isValid(char c) {
return c == '-' || c == '+';
}
}
final static class HexMask extends Mask {
@Override
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'));
}
@Override
char getChar(char c) {
if (Character.isDigit(c)) {
return c;
}
return Character.toUpperCase(c);
}
}
}