/*
* Copyright 2014-2015 CyberVision, Inc.
*
* 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 org.kaaproject.avro.ui.gwt.client.widget;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.ParseException;
import org.kaaproject.avro.ui.gwt.client.AvroUiResources.AvroUiStyle;
import org.kaaproject.avro.ui.gwt.client.util.Utils;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.text.shared.AbstractRenderer;
import com.google.gwt.text.shared.Parser;
import com.google.gwt.text.shared.Renderer;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.ValueBox;
public abstract class NumberBox<T extends Number> extends ValueBox<T> implements BlurHandler,
FocusHandler, ClickHandler, KeyDownHandler {
private AvroUiStyle style;
private String promptText;
private boolean isDecimal = false;
private Renderer<T> renderer;
protected NumberBox(AvroUiStyle style, Element element, String promptText,
final Renderer<T> renderer, final Parser<T> parser) {
super(element, renderer, parser);
sinkEvents(Event.ONPASTE);
this.style = style;
this.promptText = promptText;
this.renderer = renderer;
if (Utils.isNotBlank(promptText)) {
this.addFocusHandler(this);
this.addClickHandler(this);
setPrompts();
}
this.addKeyDownHandler(this);
this.addBlurHandler(this);
}
@Override
public void onBlur(BlurEvent event) {
if (Utils.isNotBlank(promptText) && Utils.isBlank(super.getText())) {
setPrompts();
} else {
setText(renderer.render(getValue()));
}
}
@Override
public void onFocus(FocusEvent event) {
this.setSelectionRange(0, 0);
}
@Override
public void onClick(ClickEvent event) {
if (promptText.equals(super.getText())) {
removePrompts();
}
}
@Override
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
switch (DOM.eventGetType(event)) {
case Event.ONPASTE:
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
if (Utils.isNotBlank(promptText) && Utils.isBlank(NumberBox.super.getText())) {
setPrompts();
} else {
setText(renderer.render(getValue()));
} }
});
break;
}
}
@Override
public void onKeyDown(KeyDownEvent event) {
if ( !isEnabled( ) || isReadOnly( ) )
return;
if (Utils.isNotBlank(promptText) && promptText.equals(super.getText())
&& !(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_TAB)) {
removePrompts();
}
int keyCode = event.getNativeEvent().getKeyCode();
// allow special keys
switch(keyCode) {
case KeyCodes.KEY_TAB:
case KeyCodes.KEY_BACKSPACE:
case KeyCodes.KEY_DELETE:
case KeyCodes.KEY_LEFT:
case KeyCodes.KEY_RIGHT:
case KeyCodes.KEY_UP:
case KeyCodes.KEY_DOWN:
case KeyCodes.KEY_END:
case KeyCodes.KEY_ENTER:
case KeyCodes.KEY_ESCAPE:
case KeyCodes.KEY_PAGEDOWN:
case KeyCodes.KEY_PAGEUP:
case KeyCodes.KEY_HOME:
case KeyCodes.KEY_SHIFT:
case KeyCodes.KEY_ALT:
case KeyCodes.KEY_CTRL:
return;
default:
if (event.isAltKeyDown() || (event.isControlKeyDown() &&
(keyCode == KeyCodes.KEY_C || keyCode == KeyCodes.KEY_V
|| keyCode == KeyCodes.KEY_X )))
return;
if (!event.isShiftKeyDown()) {
// check for decimal '.'
if (isDecimal() && isDot(keyCode) && !getText().contains("."))
return;
// check for negative sign '-'
if (getCursorPos() == 0 && isDash(keyCode) && !getText().startsWith("-"))
return;
// filter out non-digits
if (isDigit(keyCode)) {
return;
}
}
}
cancelKey();
}
private static boolean isDot(int keyCode) {
return keyCode == 190 || keyCode == 110;
}
private static boolean isDash(int keyCode) {
return (keyCode == (isFirefox() ? 173 : 189)) || keyCode == 109;
}
private static boolean isDigit(int keyCode) {
return keyCode >= 48 && keyCode <= 57 || keyCode >= 96 && keyCode <= 105;
}
private static native boolean isFirefox() /*-{
return navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
}-*/;
public boolean isDecimal() {
return isDecimal;
}
public void setDecimal(boolean isDecimal) {
this.isDecimal = isDecimal;
}
@Override
public String getText() {
String text = super.getText();
if (Utils.isNotBlank(promptText) && promptText.equals(text)) {
return "";
} else {
return text;
}
}
@Override
public void setText(String text) {
if (Utils.isNotBlank(promptText)) {
if (Utils.isBlank(text)) {
setPrompts();
} else {
removePrompts();
super.setText(text);
}
} else {
super.setText(text);
}
}
private void setPrompts() {
this.addStyleName(style.prompt());
super.setText(promptText);
}
private void removePrompts() {
this.removeStyleName(style.prompt());
super.setText("");
}
static class NumberRenderer<N extends Number> extends AbstractRenderer<N> {
private NumberFormat numberFormat;
private boolean hasFraction;
public NumberRenderer(String numberFormatPattern, boolean hasFraction) {
numberFormat = NumberFormat.getFormat(numberFormatPattern);
this.hasFraction = hasFraction;
if (!hasFraction) {
numberFormat.overrideFractionDigits(0);
}
}
public String render(N object) {
if (null == object) {
return "";
}
try {
if (hasFraction) {
int precision = 0;
String strNumber = object.toString();
precision = strNumber.length();
if (strNumber.contains(".")) {
precision--;
}
return numberFormat.format(new BigDecimal(object.doubleValue(), new MathContext(precision, RoundingMode.HALF_EVEN)));
} else {
return numberFormat.format(object);
}
} catch (NumberFormatException e) {
return "";
}
}
}
static abstract class NumberParser<N extends Number> implements Parser<N> {
private NumberFormat numberFormat;
public NumberParser(String numberFormatPattern, boolean hasFraction) {
numberFormat = NumberFormat.getFormat(numberFormatPattern);
if (!hasFraction) {
numberFormat.overrideFractionDigits(0);
}
}
public N parse(CharSequence object) throws ParseException {
if ("".equals(object.toString())) {
return null;
}
try {
return toNumber(numberFormat.parse(object.toString()));
} catch (NumberFormatException e) {
throw new ParseException(e.getMessage(), 0);
}
}
protected abstract N toNumber(double val);
}
static class IntegerParser extends NumberParser<Integer> {
public IntegerParser(String numberFormatPattern) {
super(numberFormatPattern, false);
}
@Override
protected Integer toNumber(double val) {
return (int) Math.rint(val);
}
}
static class LongParser extends NumberParser<Long> {
public LongParser(String numberFormatPattern) {
super(numberFormatPattern, false);
}
@Override
protected Long toNumber(double val) {
return (long) val;
}
}
static class FloatParser extends NumberParser<Float> {
public FloatParser(String numberFormatPattern) {
super(numberFormatPattern, true);
}
@Override
protected Float toNumber(double val) {
return (float) val;
}
}
static class DoubleParser extends NumberParser<Double> {
public DoubleParser(String numberFormatPattern) {
super(numberFormatPattern, true);
}
@Override
protected Double toNumber(double val) {
return val;
}
}
}