Java tutorial
/* * Copyright 2015 cruxframework.org. * * 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.cruxframework.crux.smartfaces.client.input; import java.text.ParseException; import org.cruxframework.crux.core.client.collection.Array; import org.cruxframework.crux.core.client.collection.CollectionFactory; import org.cruxframework.crux.core.client.event.paste.HasPasteHandlers; import org.cruxframework.crux.core.client.event.paste.PasteEvent; import org.cruxframework.crux.core.client.event.paste.PasteEventSourceRegister; import org.cruxframework.crux.core.client.event.paste.PasteHandler; import org.cruxframework.crux.core.client.utils.DeviceAdaptiveUtils; import org.cruxframework.crux.core.client.utils.StringUtils; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Document; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.DoubleClickEvent; import com.google.gwt.event.dom.client.DoubleClickHandler; import com.google.gwt.event.dom.client.DragEndEvent; import com.google.gwt.event.dom.client.DragEndHandler; import com.google.gwt.event.dom.client.DragEnterEvent; import com.google.gwt.event.dom.client.DragEnterHandler; import com.google.gwt.event.dom.client.DragEvent; import com.google.gwt.event.dom.client.DragHandler; import com.google.gwt.event.dom.client.DragLeaveEvent; import com.google.gwt.event.dom.client.DragLeaveHandler; import com.google.gwt.event.dom.client.DragOverEvent; import com.google.gwt.event.dom.client.DragOverHandler; import com.google.gwt.event.dom.client.DragStartEvent; import com.google.gwt.event.dom.client.DragStartHandler; import com.google.gwt.event.dom.client.DropEvent; import com.google.gwt.event.dom.client.DropHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.GestureChangeEvent; import com.google.gwt.event.dom.client.GestureChangeHandler; import com.google.gwt.event.dom.client.GestureEndEvent; import com.google.gwt.event.dom.client.GestureEndHandler; import com.google.gwt.event.dom.client.GestureStartEvent; import com.google.gwt.event.dom.client.GestureStartHandler; import com.google.gwt.event.dom.client.HasAllDragAndDropHandlers; import com.google.gwt.event.dom.client.HasAllFocusHandlers; import com.google.gwt.event.dom.client.HasAllGestureHandlers; import com.google.gwt.event.dom.client.HasAllMouseHandlers; import com.google.gwt.event.dom.client.HasAllTouchHandlers; import com.google.gwt.event.dom.client.HasChangeHandlers; import com.google.gwt.event.dom.client.HasClickHandlers; import com.google.gwt.event.dom.client.HasDoubleClickHandlers; 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.event.dom.client.KeyPressEvent; import com.google.gwt.event.dom.client.KeyPressHandler; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseMoveHandler; import com.google.gwt.event.dom.client.MouseOutEvent; import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.event.dom.client.MouseOverEvent; import com.google.gwt.event.dom.client.MouseOverHandler; import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.event.dom.client.MouseWheelEvent; import com.google.gwt.event.dom.client.MouseWheelHandler; import com.google.gwt.event.dom.client.TouchCancelEvent; import com.google.gwt.event.dom.client.TouchCancelHandler; import com.google.gwt.event.dom.client.TouchEndEvent; import com.google.gwt.event.dom.client.TouchEndHandler; import com.google.gwt.event.dom.client.TouchMoveEvent; import com.google.gwt.event.dom.client.TouchMoveHandler; import com.google.gwt.event.dom.client.TouchStartEvent; import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.i18n.client.HasDirection; import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.i18n.shared.DirectionEstimator; import com.google.gwt.i18n.shared.HasDirectionEstimator; import com.google.gwt.text.shared.AbstractRenderer; import com.google.gwt.text.shared.Parser; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Focusable; import com.google.gwt.user.client.ui.HasEnabled; import com.google.gwt.user.client.ui.HasName; import com.google.gwt.user.client.ui.HasValue; import com.google.gwt.user.client.ui.ValueBox; import com.google.gwt.user.client.ui.Widget; /** * A numeric box * @author Thiago da Rosa de Bustamante * */ public class NumberBox extends Composite implements HasEnabled, Focusable, HasValue<Number>, HasName, HasDirectionEstimator, HasDirection, HasClickHandlers, HasDoubleClickHandlers, HasAllDragAndDropHandlers, HasAllFocusHandlers, HasAllGestureHandlers, HasAllMouseHandlers, HasAllTouchHandlers, HasChangeHandlers { private static final String DEFAULT_STYLE_NAME = "faces-NumberBox"; private Box box; private boolean changeEmulation; private boolean changeEmulationHandlerInitialized = false; private Array<ChangeHandler> changeHandlers = CollectionFactory.createArray(); private Number enterValue = null; private FormatterOptions formatterOptions; private String localeDecimalSeparator = LocaleInfo.getCurrentLocale().getNumberConstants().decimalSeparator(); private String localeGroupSeparator = LocaleInfo.getCurrentLocale().getNumberConstants().groupingSeparator(); private Number maxValue; private Number minValue; private NumberRenderer renderer; private boolean valueChangeHandlerInitialized = false; public NumberBox() { this(new FormatterOptions()); } public NumberBox(FormatterOptions formatterOptions) { renderer = new NumberRenderer(this); box = new Box(renderer); EventsHandler eventsHandler = new EventsHandler(this); box.addKeyDownHandler(eventsHandler); box.addKeyPressHandler(eventsHandler); box.addKeyUpHandler(eventsHandler); box.addBlurHandler(eventsHandler); box.addPasteHandler(eventsHandler); box.addFocusHandler(eventsHandler); initWidget(box); setFormatterOptions(formatterOptions); setStyleName(DEFAULT_STYLE_NAME); fixChangeEvents(); } @Override public HandlerRegistration addBlurHandler(BlurHandler handler) { return addDomHandler(handler, BlurEvent.getType()); } @Override public HandlerRegistration addChangeHandler(ChangeHandler handler) { if (!changeEmulationHandlerInitialized) { changeEmulationHandlerInitialized = true; addDomHandler(new ChangeHandler() { public void onChange(ChangeEvent event) { handleChangeEvent(event); } }, ChangeEvent.getType()); } return handleChangeHandler(handler); } @Override public HandlerRegistration addClickHandler(ClickHandler handler) { return addDomHandler(handler, ClickEvent.getType()); } @Override public HandlerRegistration addDoubleClickHandler(DoubleClickHandler handler) { return addDomHandler(handler, DoubleClickEvent.getType()); } @Override public HandlerRegistration addDragEndHandler(DragEndHandler handler) { return addBitlessDomHandler(handler, DragEndEvent.getType()); } @Override public HandlerRegistration addDragEnterHandler(DragEnterHandler handler) { return addBitlessDomHandler(handler, DragEnterEvent.getType()); } @Override public HandlerRegistration addDragHandler(DragHandler handler) { return addBitlessDomHandler(handler, DragEvent.getType()); } @Override public HandlerRegistration addDragLeaveHandler(DragLeaveHandler handler) { return addBitlessDomHandler(handler, DragLeaveEvent.getType()); } @Override public HandlerRegistration addDragOverHandler(DragOverHandler handler) { return addBitlessDomHandler(handler, DragOverEvent.getType()); } @Override public HandlerRegistration addDragStartHandler(DragStartHandler handler) { return addBitlessDomHandler(handler, DragStartEvent.getType()); } @Override public HandlerRegistration addDropHandler(DropHandler handler) { return addBitlessDomHandler(handler, DropEvent.getType()); } @Override public HandlerRegistration addFocusHandler(FocusHandler handler) { return addDomHandler(handler, FocusEvent.getType()); } @Override public HandlerRegistration addGestureChangeHandler(GestureChangeHandler handler) { return addDomHandler(handler, GestureChangeEvent.getType()); } @Override public HandlerRegistration addGestureEndHandler(GestureEndHandler handler) { return addDomHandler(handler, GestureEndEvent.getType()); } @Override public HandlerRegistration addGestureStartHandler(GestureStartHandler handler) { return addDomHandler(handler, GestureStartEvent.getType()); } @Override public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) { return addDomHandler(handler, MouseDownEvent.getType()); } @Override public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) { return addDomHandler(handler, MouseMoveEvent.getType()); } @Override public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) { return addDomHandler(handler, MouseOutEvent.getType()); } @Override public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) { return addDomHandler(handler, MouseOverEvent.getType()); } @Override public HandlerRegistration addMouseUpHandler(MouseUpHandler handler) { return addDomHandler(handler, MouseUpEvent.getType()); } @Override public HandlerRegistration addMouseWheelHandler(MouseWheelHandler handler) { return addDomHandler(handler, MouseWheelEvent.getType()); } @Override public HandlerRegistration addTouchCancelHandler(TouchCancelHandler handler) { return addDomHandler(handler, TouchCancelEvent.getType()); } @Override public HandlerRegistration addTouchEndHandler(TouchEndHandler handler) { return addDomHandler(handler, TouchEndEvent.getType()); } @Override public HandlerRegistration addTouchMoveHandler(TouchMoveHandler handler) { return addDomHandler(handler, TouchMoveEvent.getType()); } @Override public HandlerRegistration addTouchStartHandler(TouchStartHandler handler) { return addDomHandler(handler, TouchStartEvent.getType()); } @Override public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Number> handler) { // Initialization code if (!valueChangeHandlerInitialized) { valueChangeHandlerInitialized = true; addChangeHandler(new ChangeHandler() { public void onChange(ChangeEvent event) { ValueChangeEvent.fire(NumberBox.this, getValue()); } }); } return addHandler(handler, ValueChangeEvent.getType()); } @Override public Direction getDirection() { return box.getDirection(); } @Override public DirectionEstimator getDirectionEstimator() { return box.getDirectionEstimator(); } public Number getMaxValue() { return maxValue; } public Number getMinValue() { return minValue; } @Override public String getName() { return box.getName(); } @Override public int getTabIndex() { return box.getTabIndex(); } @Override public Number getValue() { return box.getValue(); } @Override public boolean isEnabled() { return box.isEnabled(); } @Override public void setAccessKey(char key) { box.setAccessKey(key); } @Override public void setDirection(Direction direction) { box.setDirection(direction); } @Override public void setDirectionEstimator(boolean enabled) { box.setDirectionEstimator(enabled); } @Override public void setDirectionEstimator(DirectionEstimator directionEstimator) { box.setDirectionEstimator(directionEstimator); } @Override public void setEnabled(boolean enabled) { box.setEnabled(enabled); } @Override public void setFocus(boolean focused) { box.setFocus(focused); } public void setFormatterOptions(FormatterOptions formatterOptions) { StringBuilder pattern = new StringBuilder("0"); if (formatterOptions.showGroupSeparators) { for (int i = 0; i < formatterOptions.groupSize - 1; i++) { pattern.insert(0, "#"); } pattern.insert(0, "#,"); if (StringUtils.isEmpty(formatterOptions.groupSeparator)) { formatterOptions.groupSeparator = localeGroupSeparator; } } if (formatterOptions.fractionDigits > 0) { pattern.append("."); for (int i = 0; i < formatterOptions.fractionDigits; i++) { pattern.append("0"); } if (StringUtils.isEmpty(formatterOptions.decimalSeparator)) { formatterOptions.decimalSeparator = localeDecimalSeparator; } } assert (!StringUtils.unsafeEquals(formatterOptions.decimalSeparator, formatterOptions.groupSeparator)) : "Invalid options. Decimal separator can not be equals to group separator."; NumberFormat numberFormat = NumberFormat.getFormat(pattern.toString()); renderer.setNumberFormat(numberFormat); this.formatterOptions = formatterOptions; } public void setMaxValue(Number maxValue) { this.maxValue = maxValue; Number value = getValue(); if (value != null && maxValue != null && value.doubleValue() > maxValue.doubleValue()) { setValue(maxValue); } } public void setMinValue(Number minValue) { this.minValue = minValue; Number value = getValue(); if (value != null && minValue != null && value.doubleValue() < minValue.doubleValue()) { setValue(minValue); } } @Override public void setName(String name) { box.setName(name); } @Override public void setTabIndex(int index) { box.setTabIndex(index); } @Override public void setValue(Number value) { setValue(value, false); } @Override public void setValue(Number value, boolean fireEvents) { setValue(value, fireEvents, true); } private void fireChangeEvent() { this.changeEmulation = true; Event changeEvent = Document.get().createChangeEvent().cast(); getElement().dispatchEvent(changeEvent); this.changeEmulation = false; } /** * The key events handling performed by this widget prevents the browser to call the change event. * So we need to force its call when blur event occurs. */ private void fixChangeEvents() { addBlurHandler(new BlurHandler() { public void onBlur(BlurEvent event) { Number value = getValue(); if (value != enterValue) { if (value == null || enterValue == null || !enterValue.equals(value)) { fireChangeEvent(); } } } }); } private void handleChangeEvent(ChangeEvent event) { if (this.changeEmulation) { for (int i = 0; i < changeHandlers.size(); i++) { changeHandlers.get(i).onChange(event); } } } private HandlerRegistration handleChangeHandler(final ChangeHandler handler) { changeHandlers.add(handler); return new HandlerRegistration() { @Override public void removeHandler() { changeHandlers.remove(handler); } }; } private void saveEnterValue() { this.enterValue = getValue(); } private void setValue(Number value, boolean fireEvents, boolean ensureValueConstraints) { if (ensureValueConstraints) { if (value != null && maxValue != null && value.doubleValue() > maxValue.doubleValue()) { value = maxValue; } if (value != null && minValue != null && value.doubleValue() < minValue.doubleValue()) { value = minValue; } } box.setValue(value, fireEvents); } public static class FormatterOptions { public static final boolean DEFAULT_ALLOW_NEGATIVES = true; public static final int DEFAULT_FRACTION_DIGITS = 0; public static final int DEFAULT_GROUP_SIZE = 3; public static final boolean DEFAULT_SHOW_GROUP_SEPARATOR = true; private boolean allowNegatives = DEFAULT_ALLOW_NEGATIVES; private String decimalSeparator = null; private int fractionDigits = DEFAULT_FRACTION_DIGITS; private String groupSeparator = null; private int groupSize = DEFAULT_GROUP_SIZE; private boolean showGroupSeparators = DEFAULT_SHOW_GROUP_SEPARATOR; public FormatterOptions() { } public String getDecimalSeparator() { return decimalSeparator; } public int getFractionDigits() { return fractionDigits; } public String getGroupSeparator() { return groupSeparator; } public int getGroupSize() { return groupSize; } public boolean isAllowNegatives() { return allowNegatives; } public boolean isShowGroupSeparators() { return showGroupSeparators; } public void setAllowNegatives(boolean allowNegatives) { this.allowNegatives = allowNegatives; } public void setDecimalSeparator(String decimalSeparator) { this.decimalSeparator = decimalSeparator; } public void setFractionDigits(int fractionDigits) { this.fractionDigits = fractionDigits; } public void setGroupSeparator(String groupSeparator) { this.groupSeparator = groupSeparator; } public void setGroupSize(int groupSize) { this.groupSize = groupSize; } public void setShowGroupSeparators(boolean showGroupSeparators) { this.showGroupSeparators = showGroupSeparators; } } public static class NumberBoxType { public void handleType(Widget widget) { widget.getElement().setAttribute("inputmode", "numeric"); } } static class Box extends ValueBox<Number> implements HasPasteHandlers { public Box(NumberRenderer renderer) { super(Document.get().createTextInputElement(), renderer, renderer); NumberBoxType numberBoxType = GWT.create(NumberBoxType.class); numberBoxType.handleType(this); PasteEventSourceRegister.registerPasteEventSource(this, getElement()); } @Override public HandlerRegistration addPasteHandler(PasteHandler handler) { return addHandler(handler, PasteEvent.getType()); } } static class EventsHandler implements KeyDownHandler, KeyPressHandler, KeyUpHandler, BlurHandler, FocusHandler, PasteHandler { private static final int DASH = 189; private boolean ignored; private boolean isControlChar; private NumberBox numberBox; private String text; public EventsHandler(NumberBox numberBox) { this.numberBox = numberBox; } @Override public void onBlur(BlurEvent event) { if (StringUtils.unsafeEquals("-", numberBox.box.getText())) { numberBox.setValue(null); } else { Number value = numberBox.getValue(); if (value != null && ((numberBox.maxValue != null && value.doubleValue() > numberBox.maxValue.doubleValue()) || (numberBox.minValue != null && value.doubleValue() < numberBox.minValue.doubleValue()))) { numberBox.setValue(null); } } } @Override public void onFocus(FocusEvent event) { numberBox.saveEnterValue(); } @Override public void onKeyDown(KeyDownEvent event) { ignored = false; int keyCode = event.getNativeKeyCode(); boolean isNumber = (keyCode >= KeyCodes.KEY_ZERO && keyCode <= KeyCodes.KEY_NINE) || (keyCode >= KeyCodes.KEY_NUM_ZERO && keyCode <= KeyCodes.KEY_NUM_NINE); boolean isMinus = (keyCode == KeyCodes.KEY_NUM_MINUS || keyCode == DASH); isControlChar = (keyCode <= KeyCodes.KEY_DELETE && keyCode != KeyCodes.KEY_SPACE) || event.isControlKeyDown() || event.isAltKeyDown() || event.isMetaKeyDown(); if (isMinus && numberBox.formatterOptions.allowNegatives) { Number numVal = numberBox.box.getValue(); double value = (numVal != null ? numVal.doubleValue() : 0); if (value < 0) { ignored = true; } else if (value == 0) { numberBox.box.setText("-"); ignored = true; } } else if (keyCode == KeyCodes.KEY_DELETE || keyCode == KeyCodes.KEY_BACKSPACE) { Number numVal = numberBox.box.getValue(); double value = (numVal != null ? numVal.doubleValue() : 0); if (value == 0) { //This is a workaround to browsers that do not fire onKeyPress event //@see issue github #798 if (!DeviceAdaptiveUtils.isInternetExplorerMobile()) { numberBox.setValue(null); } ignored = true; } } else if (!isNumber && !isControlChar) { ignored = true; } if (ignored) { event.preventDefault(); } } @Override public void onKeyPress(KeyPressEvent event) { if (!isControlChar) { String newText = processKey(numberBox.box.getText(), event.getCharCode()); updateTextValue(newText); event.preventDefault(); } text = numberBox.box.getText(); } @Override public void onKeyUp(KeyUpEvent event) { if (isControlChar && !ignored) { String newText = numberBox.box.getText(); if (!StringUtils.unsafeEquals(newText, text)) { updateTextValue(newText); text = numberBox.box.getText(); } } } @Override public void onPaste(PasteEvent event) { String newText = numberBox.box.getText(); if (StringUtils.isEmpty(newText)) { numberBox.setValue(null); } else { if (updateTextValue(newText)) { text = numberBox.box.getText(); } else { updateTextValue(text); } } ignored = true; } private String processKey(String text, char charCode) { StringBuilder result = new StringBuilder(text); int cursorPos = numberBox.box.getCursorPos(); if (numberBox.box.getSelectionLength() > 0) { result.delete(cursorPos, cursorPos + numberBox.box.getSelectionLength()); } if (charCode == '-') { result.insert(0, charCode); } else { result.insert(cursorPos, charCode); } return result.toString(); } private boolean updateTextValue(String text) { boolean ret = false; if (numberBox.formatterOptions.showGroupSeparators) { text = text.replace(numberBox.formatterOptions.groupSeparator, ""); } if (numberBox.formatterOptions.fractionDigits > 0) { text = text.replace(numberBox.formatterOptions.decimalSeparator, ""); } try { double number = Long.parseLong(text); if (numberBox.formatterOptions.fractionDigits > 0) { number = number / Math.pow(10.0, numberBox.formatterOptions.fractionDigits); } if (numberBox.maxValue == null || number <= numberBox.maxValue.doubleValue()) { numberBox.setValue(number, false, false); ret = true; } } catch (NumberFormatException e) { ret = numberBox.getValue() == null; numberBox.setValue(null); } return ret; } } static class NumberRenderer extends AbstractRenderer<Number> implements Parser<Number> { private static final String SWAP_DECIMAL_SEPARATOR = "_|_"; private NumberFormat format; private NumberBox numberBox; public NumberRenderer(NumberBox numberBox) { this.numberBox = numberBox; } @Override public Number parse(CharSequence text) throws ParseException { if (text.length() == 0) { return null; } try { String toParse = text.toString(); toParse = changeText(toParse, false); return format.parse(toParse); } catch (NumberFormatException e) { throw new ParseException(e.getMessage(), 0); } } public String render(Number object) { if (object == null) { return ""; } String result = format.format(object); return changeText(result, true); } public void setNumberFormat(NumberFormat format) { this.format = format; } private String changeText(String text, boolean toString) { boolean needsDecimalReplacement = (numberBox.formatterOptions.fractionDigits > 0 && !StringUtils .unsafeEquals(numberBox.formatterOptions.decimalSeparator, numberBox.localeDecimalSeparator)); boolean needsSeparatorReplacement = (numberBox.formatterOptions.showGroupSeparators && !StringUtils .unsafeEquals(numberBox.formatterOptions.groupSeparator, numberBox.localeGroupSeparator)); if (needsDecimalReplacement) { if (toString) { text = text.replace(".", SWAP_DECIMAL_SEPARATOR); } else { text = text.replace(numberBox.formatterOptions.decimalSeparator, SWAP_DECIMAL_SEPARATOR); } } if (needsSeparatorReplacement) { if (toString) { text = text.replace(",", numberBox.formatterOptions.groupSeparator); } else { text = text.replace(numberBox.formatterOptions.groupSeparator, ","); } } if (needsDecimalReplacement) { if (toString) { text = text.replace(SWAP_DECIMAL_SEPARATOR, numberBox.formatterOptions.decimalSeparator); } else { text = text.replace(SWAP_DECIMAL_SEPARATOR, "."); } } return text; } } }