blob: d24f2a3655087b65c66a71a319c9d1e19dfc20a5 [file] [log] [blame]
/*
* Copyright 2009 Google 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 com.google.gwt.user.client.ui;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.editor.client.IsEditor;
import com.google.gwt.editor.ui.client.adapters.ValueBoxEditor;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.HasChangeHandlers;
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.AutoDirectionHandler;
import com.google.gwt.i18n.client.BidiPolicy;
import com.google.gwt.i18n.client.BidiUtils;
import com.google.gwt.i18n.shared.DirectionEstimator;
import com.google.gwt.i18n.shared.HasDirectionEstimator;
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.impl.TextBoxImpl;
import java.text.ParseException;
/**
* Abstract base class for all text entry widgets.
*
* <h3>Use in UiBinder Templates</h3>
*
* @param <T> the value type
*/
@SuppressWarnings("deprecation")
public class ValueBoxBase<T> extends FocusWidget implements
HasChangeHandlers, HasName, HasDirectionEstimator,
HasValue<T>, AutoDirectionHandler.Target, IsEditor<ValueBoxEditor<T>> {
/**
* Alignment values for {@link ValueBoxBase#setAlignment}.
*/
public enum TextAlignment {
CENTER {
String getTextAlignString() {
return "center";
}
},
JUSTIFY {
String getTextAlignString() {
return "justify";
}
},
LEFT {
String getTextAlignString() {
return "left";
}
},
RIGHT {
String getTextAlignString() {
return "right";
}
};
abstract String getTextAlignString();
}
private static TextBoxImpl impl = GWT.create(TextBoxImpl.class);
private final AutoDirectionHandler autoDirHandler;
private final Parser<T> parser;
private final Renderer<T> renderer;
private Event currentEvent;
private boolean valueChangeHandlerInitialized;
/**
* Creates a value box that wraps the given browser element handle. This is
* only used by subclasses.
*
* @param elem the browser element to wrap
*/
protected ValueBoxBase(Element elem, Renderer<T> renderer, Parser<T> parser) {
super(elem);
autoDirHandler = AutoDirectionHandler.addTo(this,
BidiPolicy.isBidiEnabled());
this.renderer = renderer;
this.parser = parser;
}
public HandlerRegistration addChangeHandler(ChangeHandler handler) {
return addDomHandler(handler, ChangeEvent.getType());
}
public HandlerRegistration addValueChangeHandler(ValueChangeHandler<T> handler) {
// Initialization code
if (!valueChangeHandlerInitialized) {
valueChangeHandlerInitialized = true;
addChangeHandler(new ChangeHandler() {
public void onChange(ChangeEvent event) {
ValueChangeEvent.fire(ValueBoxBase.this, getValue());
}
});
}
return addHandler(handler, ValueChangeEvent.getType());
}
/**
* Returns an Editor that is backed by the ValueBoxBase. The default
* implementation returns {@link ValueBoxEditor#of(ValueBoxBase)}. Subclasses
* may override this method to provide custom error-handling when using the
* Editor framework.
*/
public ValueBoxEditor<T> asEditor() {
return ValueBoxEditor.of(this);
}
/**
* If a keyboard event is currently being handled on this text box, calling
* this method will suppress it. This allows listeners to easily filter
* keyboard input.
*/
public void cancelKey() {
if (currentEvent != null) {
DOM.eventPreventDefault(currentEvent);
}
}
/**
* Gets the current position of the cursor (this also serves as the beginning
* of the text selection).
*
* @return the cursor's position
*/
public int getCursorPos() {
return impl.getCursorPos(getElement());
}
public Direction getDirection() {
return BidiUtils.getDirectionOnElement(getElement());
}
/**
* Gets the direction estimation model of the auto-dir handler.
*/
public DirectionEstimator getDirectionEstimator() {
return autoDirHandler.getDirectionEstimator();
}
public String getName() {
return DOM.getElementProperty(getElement(), "name");
}
/**
* Gets the text currently selected within this text box.
*
* @return the selected text, or an empty string if none is selected
*/
public String getSelectedText() {
int start = getCursorPos();
if (start < 0) {
return "";
}
int length = getSelectionLength();
return getText().substring(start, start + length);
}
/**
* Gets the length of the current text selection.
*
* @return the text selection length
*/
public int getSelectionLength() {
return impl.getSelectionLength(getElement());
}
public String getText() {
return DOM.getElementProperty(getElement(), "value");
}
/**
* Return the parsed value, or null if the field is empty or parsing fails.
*/
public T getValue() {
try {
return getValueOrThrow();
} catch (ParseException e) {
return null;
}
}
/**
* Return the parsed value, or null if the field is empty.
*
* @throws ParseException if the value cannot be parsed
*/
public T getValueOrThrow() throws ParseException {
String text = getText();
T parseResult = parser.parse(text);
if ("".equals(text)) {
return null;
}
return parseResult;
}
/**
* Determines whether or not the widget is read-only.
*
* @return <code>true</code> if the widget is currently read-only,
* <code>false</code> if the widget is currently editable
*/
public boolean isReadOnly() {
return DOM.getElementPropertyBoolean(getElement(), "readOnly");
}
@Override
public void onBrowserEvent(Event event) {
int type = DOM.eventGetType(event);
if ((type & Event.KEYEVENTS) != 0) {
// Fire the keyboard event. Hang on to the current event object so that
// cancelKey() and setKey() can be implemented.
currentEvent = event;
// Call the superclass' onBrowserEvent as that includes the key event
// handlers.
super.onBrowserEvent(event);
currentEvent = null;
} else {
// Handles Focus and Click events.
super.onBrowserEvent(event);
}
}
/**
* @deprecated Use the {@link HandlerRegistration#removeHandler} method on the
* object returned by {@link #addChangeHandler} instead
*/
@Deprecated
public void removeChangeListener(ChangeListener listener) {
ListenerWrapper.WrappedChangeListener.remove(this, listener);
}
/**
* Selects all of the text in the box.
*
* This will only work when the widget is attached to the document and not
* hidden.
*/
public void selectAll() {
int length = getText().length();
if (length > 0) {
setSelectionRange(0, length);
}
}
public void setAlignment(TextAlignment align) {
DOM.setStyleAttribute(getElement(), "textAlign", align.getTextAlignString());
}
/**
* Sets the cursor position.
*
* This will only work when the widget is attached to the document and not
* hidden.
*
* @param pos the new cursor position
*/
public void setCursorPos(int pos) {
setSelectionRange(pos, 0);
}
public void setDirection(Direction direction) {
BidiUtils.setDirectionOnElement(getElement(), direction);
}
/**
* Toggles on / off direction estimation.
*/
public void setDirectionEstimator(boolean enabled) {
autoDirHandler.setDirectionEstimator(enabled);
}
/**
* Sets the direction estimation model of the auto-dir handler.
*/
public void setDirectionEstimator(DirectionEstimator directionEstimator) {
autoDirHandler.setDirectionEstimator(directionEstimator);
}
/**
* If a keyboard event is currently being handled by the text box, this method
* replaces the unicode character or key code associated with it. This allows
* listeners to easily filter keyboard input.
*
* @param key the new key value
* @deprecated this method only works in IE and should not have been added to
* the API
*/
@Deprecated
public void setKey(char key) {
if (currentEvent != null) {
DOM.eventSetKeyCode(currentEvent, key);
}
}
public void setName(String name) {
DOM.setElementProperty(getElement(), "name", name);
}
/**
* Turns read-only mode on or off.
*
* @param readOnly if <code>true</code>, the widget becomes read-only; if
* <code>false</code> the widget becomes editable
*/
public void setReadOnly(boolean readOnly) {
DOM.setElementPropertyBoolean(getElement(), "readOnly", readOnly);
String readOnlyStyle = "readonly";
if (readOnly) {
addStyleDependentName(readOnlyStyle);
} else {
removeStyleDependentName(readOnlyStyle);
}
}
/**
* Sets the range of text to be selected.
*
* This will only work when the widget is attached to the document and not
* hidden.
*
* @param pos the position of the first character to be selected
* @param length the number of characters to be selected
*/
public void setSelectionRange(int pos, int length) {
// Setting the selection range will not work for unattached elements.
if (!isAttached()) {
return;
}
if (length < 0) {
throw new IndexOutOfBoundsException(
"Length must be a positive integer. Length: " + length);
}
if (pos < 0 || length + pos > getText().length()) {
throw new IndexOutOfBoundsException("From Index: " + pos + " To Index: "
+ (pos + length) + " Text Length: " + getText().length());
}
impl.setSelectionRange(getElement(), pos, length);
}
/**
* Sets this object's text. Note that some browsers will manipulate the text
* before adding it to the widget. For example, most browsers will strip all
* <code>\r</code> from the text, except IE which will add a <code>\r</code>
* before each <code>\n</code>. Use {@link #getText()} to get the text
* directly from the widget.
*
* @param text the object's new text
*/
public void setText(String text) {
DOM.setElementProperty(getElement(), "value", text != null ? text : "");
autoDirHandler.refreshDirection();
}
public void setValue(T value) {
setValue(value, false);
}
public void setValue(T value, boolean fireEvents) {
T oldValue = getValue();
setText(renderer.render(value));
if (fireEvents) {
ValueChangeEvent.fireIfNotEqual(this, oldValue, value);
}
}
protected TextBoxImpl getImpl() {
return impl;
}
@Override
protected void onLoad() {
super.onLoad();
autoDirHandler.refreshDirection();
}
}