io.guise.framework.component.TextControl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of guise-framework Show documentation
Show all versions of guise-framework Show documentation
Guise™ Internet application framework.
/*
* Copyright © 2005-2008 GlobalMentor, 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 io.guise.framework.component;
import java.beans.PropertyVetoException;
import static com.globalmentor.java.Classes.*;
import com.globalmentor.io.*;
import com.globalmentor.net.MediaType;
import io.guise.framework.component.transfer.*;
import io.guise.framework.converter.*;
import io.guise.framework.model.*;
import static com.globalmentor.text.Text.*;
/**
* Control to accept text input from the user representing a particular value type. This control keeps track of literal text entered by the user, distinct from
* the value stored in the model. If line wrap is not specified in the constructor, it defaults to true
. If multiline is not specified in the
* constructor, it defaults to true
only when there is more than one row and line wrap is turned off. Default converters are available for the
* following types:
*
* char[]
* java.lang.Boolean
* java.lang.Float
* java.lang.Integer
* java.lang.String
*
* This component installs a default export strategy supporting export of the following content types:
*
* text/plain
*
* This component installs a default import strategy supporting import of the following content types:
*
* text/*
*
* This control uses a single line feed character to represent each line break.
* @param The type of value the input text is to represent.
* @author Garret Wilson
*/
public class TextControl extends AbstractTextControl {
/** The line wrap bound property. */
public static final String LINE_WRAP_PROPERTY = getPropertyName(TextControl.class, "lineWrap");
/** The masked bound property. */
public static final String MASKED_PROPERTY = getPropertyName(TextControl.class, "masked");
/** The maximum length bound property. */
public static final String MAXIMUM_LENGTH_PROPERTY = getPropertyName(TextControl.class, "maximumLength");
/** The multiline bound property. */
public static final String MULTILINE_PROPERTY = getPropertyName(TextControl.class, "multiline");
/** The row count bound property. */
public static final String ROW_COUNT_PROPERTY = getPropertyName(TextControl.class, "rowCount");
/** Whether the user input text is masked to prevent viewing of the literal entered value. */
private boolean masked = false;
/** @return Whether the user input text is masked to prevent viewing of the literal entered value. */
public boolean isMasked() {
return masked;
}
/**
* Sets whether the user input text is masked to prevent viewing of the literal entered value. This is a bound property of type Boolean
.
* @param newMasked true
if the user input text should be masked.
* @see #MASKED_PROPERTY
*/
public void setMasked(final boolean newMasked) {
if(masked != newMasked) { //if the value is really changing
final boolean oldEnabled = masked; //get the old value
masked = newMasked; //actually change the value
firePropertyChange(MASKED_PROPERTY, Boolean.valueOf(oldEnabled), Boolean.valueOf(newMasked)); //indicate that the value changed
}
}
/** The maximum number of input characters to allow, or -1 if there is no maximum length. */
private int maximumLength = -1;
/** @return The maximum number of input characters to allow, or -1 if there is no maximum length. */
public int getMaximumLength() {
return maximumLength;
}
/**
* Sets the maximum number of input characters to allow. This is a bound property of type Integer
.
* @param newMaximumLength The new maximum number of input characters to allow, or -1 if there is no maximum length.
* @see #MAXIMUM_LENGTH_PROPERTY
*/
public void setMaximumLength(final int newMaximumLength) {
if(maximumLength != newMaximumLength) { //if the value is really changing
final int oldMaximumLength = maximumLength; //get the old value
maximumLength = newMaximumLength; //actually change the value
firePropertyChange(MAXIMUM_LENGTH_PROPERTY, new Integer(oldMaximumLength), new Integer(newMaximumLength)); //indicate that the value changed
}
}
/** Whether lines will be logically wrapped if needed in the view. */
private boolean lineWrap;
/** @return Whether lines will be logically wrapped in the view if needed. */
public boolean isLineWrap() {
return lineWrap;
}
/**
* Sets whether lines will be logically wrapped in the view if needed. This is a bound property of type Boolean
.
* @param newLineWrap Whether lines should be logically wrapped in the view if needed.
* @see #LINE_WRAP_PROPERTY
*/
public void setLineWrap(final boolean newLineWrap) {
if(lineWrap != newLineWrap) { //if the value is really changing
final boolean oldLineWrap = lineWrap; //get the old value
lineWrap = newLineWrap; //actually change the value
firePropertyChange(LINE_WRAP_PROPERTY, oldLineWrap, newLineWrap); //indicate that the value changed
}
}
/** Whether the user is allowed to enter multiple physical lines if the control has multiple rows. */
private boolean multiline;
/** @return Whether the user is allowed to enter multiple physical lines if the control has multiple rows. */
public boolean isMultiline() {
return multiline;
}
/**
* Sets whether the user is allowed to enter multiple physical lines if the control has multiple rows. This is a bound property of type Boolean
.
* @param newMultiline Whether the user should be allowed to enter multiple physical lines if the control has multiple rows.
* @see #MULTILINE_PROPERTY
*/
public void setMultiline(final boolean newMultiline) {
if(multiline != newMultiline) { //if the value is really changing
final boolean oldMultiline = multiline; //get the old value
multiline = newMultiline; //actually change the value
firePropertyChange(MULTILINE_PROPERTY, oldMultiline, newMultiline); //indicate that the value changed
}
}
/** The estimated number of rows requested to be visible, or -1 if no row count is specified. */
private int rowCount;
/** @return The estimated number of rows requested to be visible, or -1 if no row count is specified. */
public int getRowCount() {
return rowCount;
}
/**
* Sets the estimated number of rows requested to be visible. This is a bound property of type Integer
.
* @param newRowCount The new requested number of visible rows, or -1 if no row count is specified.
* @see #ROW_COUNT_PROPERTY
*/
public void setRowCount(final int newRowCount) {
if(rowCount != newRowCount) { //if the value is really changing
final int oldRowCount = rowCount; //get the old value
rowCount = newRowCount; //actually change the value
firePropertyChange(ROW_COUNT_PROPERTY, new Integer(oldRowCount), new Integer(newRowCount)); //indicate that the value changed
}
}
/** The default export strategy for this component type. */
protected static final ExportStrategy> DEFAULT_EXPORT_STRATEGY = new ExportStrategy>() {
@Override
public Transferable> exportTransfer(final TextControl component) {
return new DefaultTransferable(component); //return a default transferable for this component
}
};
/** The default import strategy for this component type. */
protected static final ImportStrategy> DEFAULT_IMPORT_STRATEGY = new ImportStrategy>() { //add a new import strategy for this component
/**
* {@inheritDoc}
*
* This implementation accepts all transferables providing text/*
data.
*
*/
@Override
public boolean canImportTransfer(final TextControl component, final Transferable transferable) {
return transferable.canTransfer(MediaType.of(MediaType.TEXT_PRIMARY_TYPE, MediaType.WILDCARD_SUBTYPE)); //we can import any text
}
/**
* {@inheritDoc}
*
* This implementation imports the first available text/*
data.
*
*/
@Override
public boolean importTransfer(final TextControl component, final Transferable transferable) {
boolean imported = false; //we'll assume we didn't import anything
Object data = null; //we'll store here any data we retrieve
if(transferable.canTransfer(PLAIN_MEDIA_TYPE)) { //text/plain is our favorite type; if we can import it
data = transferable.transfer(PLAIN_MEDIA_TYPE); //transfer the data
imported = true; //indicate that we transported data
} else { //otherwise, check for text/* types
for(final MediaType contentType : transferable.getContentTypes()) { //for each available content type
if(contentType.matches(MediaType.TEXT_PRIMARY_TYPE, MediaType.WILDCARD_SUBTYPE)) { //if this is a text content type
data = transferable.transfer(contentType); //transfer the data
imported = true; //indicate that we transported data
break; //stop looking for a match
}
}
}
if(imported && data != null) { //if we transferred data
final String oldText = component.getText(); //get the current text
final StringBuilder newTextStringBuilder = new StringBuilder(); //create a string builder to collect our new information
if(oldText != null) { //if there is content already
newTextStringBuilder.append(oldText); //add the old content
}
newTextStringBuilder.append(data); //append the data
final String newText = newTextStringBuilder.toString(); //get the new text
try {
component.setTextValue(newText); //update the literal text of the component, which will in turn update the provisional text of the component, and then update the value
} catch(final ConversionException conversionException) { //if there is a conversion error
component.setNotification(new Notification(conversionException)); //add this error to the component
imported = false; //transfer was unsuccessful
} catch(final PropertyVetoException propertyVetoException) { //if there is a veto
final Throwable cause = propertyVetoException.getCause(); //get the cause of the veto, if any
component.setNotification(new Notification(cause != null ? cause : propertyVetoException)); //add notification of the error to the component
imported = false; //transfer was unsuccessful
}
}
return imported; //indicate whether we were able to find any information to transfer
}
/**
* Imports the given text into the given component.
* @param component The component into which the data will be transferred.
* @param data The data to be transferred.
*/
protected void importTransfer(final TextControl component, final Object data) {
if(data != null) { //if we transferred data
final String oldText = component.getText(); //get the current text
final StringBuilder newTextStringBuilder = new StringBuilder(); //create a string builder to collect our new information
if(oldText != null) { //if there is content already
newTextStringBuilder.append(oldText); //add the old content
}
newTextStringBuilder.append(data); //append the data
final String newText = newTextStringBuilder.toString(); //get the new text
try {
component.setTextValue(newText); //update the literal text of the component, which will in turn update the provisional text of the component, and then update the value
} catch(final ConversionException conversionException) { //if there is a conversion error
component.setNotification(new Notification(conversionException)); //add this error to the component
} catch(final PropertyVetoException propertyVetoException) { //if there is a veto
final Throwable cause = propertyVetoException.getCause(); //get the cause of the veto, if any
component.setNotification(new Notification(cause != null ? cause : propertyVetoException)); //add notification of the error to the component
}
}
}
};
/**
* Value class constructor with a default data model to represent a given type and a default converter.
* @param valueClass The class indicating the type of value held in the model.
* @throws NullPointerException if the given value class is null
.
*/
public TextControl(final Class valueClass) {
this(valueClass, null); //construct the class with a null default value
}
/**
* Value class and default value constructor with a default data model to represent a given type and a default converter.
* @param valueClass The class indicating the type of value held in the model.
* @param defaultValue The default value, which will not be validated.
* @throws NullPointerException if the given value class is null
.
*/
public TextControl(final Class valueClass, final V defaultValue) {
this(new DefaultValueModel(valueClass, defaultValue)); //construct the class with a default model
}
/**
* Value class and column count constructor with one row and a default converter.
* @param valueClass The class indicating the type of value held in the model.
* @param columnCount The requested number of visible columns, or -1 if no column count is specified.
* @throws NullPointerException if the given value class is null
.
*/
public TextControl(final Class valueClass, final int columnCount) {
this(valueClass, 1, columnCount); //construct the class with one row
}
/**
* Value class, row count, and column count constructor with a default converter.
* @param valueClass The class indicating the type of value held in the model.
* @param rowCount The requested number of visible rows, or -1 if no row count is specified.
* @param columnCount The requested number of visible columns, or -1 if no column count is specified.
* @throws NullPointerException if the given value class is null
.
*/
public TextControl(final Class valueClass, final int rowCount, final int columnCount) {
this(valueClass, null, rowCount, columnCount); //construct the class with a null default value
}
/**
* Value class, defaultValue, and column count constructor with one row a default converter.
* @param valueClass The class indicating the type of value held in the model.
* @param defaultValue The default value, which will not be validated.
* @param columnCount The requested number of visible columns, or -1 if no column count is specified.
* @throws NullPointerException if the given value class is null
.
*/
public TextControl(final Class valueClass, final V defaultValue, final int columnCount) {
this(valueClass, defaultValue, 1, columnCount); //construct the class with one row
}
/**
* Value class, defaultValue, row count, and column count constructor with a default converter.
* @param valueClass The class indicating the type of value held in the model.
* @param defaultValue The default value, which will not be validated.
* @param rowCount The requested number of visible rows, or -1 if no row count is specified.
* @param columnCount The requested number of visible columns, or -1 if no column count is specified.
* @throws NullPointerException if the given value class is null
.
*/
public TextControl(final Class valueClass, final V defaultValue, final int rowCount, final int columnCount) {
this(valueClass, defaultValue, rowCount, columnCount, true); //default to line-wrapping
}
/**
* Value class, row count, column count, and line wrap constructor with a default converter.
* @param valueClass The class indicating the type of value held in the model.
* @param rowCount The requested number of visible rows, or -1 if no row count is specified.
* @param columnCount The requested number of visible columns, or -1 if no column count is specified.
* @param lineWrap Whether lines should be wrapped in the view if needed.
* @throws NullPointerException if the given value class is null
.
*/
public TextControl(final Class valueClass, final int rowCount, final int columnCount, final boolean lineWrap) {
this(valueClass, null, rowCount, columnCount, lineWrap); //construct the class with a null default value
}
/**
* Value class, default value, row count, column count, and line wrap constructor with a default converter.
* @param valueClass The class indicating the type of value held in the model.
* @param defaultValue The default value, which will not be validated.
* @param rowCount The requested number of visible rows, or -1 if no row count is specified.
* @param columnCount The requested number of visible columns, or -1 if no column count is specified.
* @param lineWrap Whether lines should be wrapped in the view if needed.
* @throws NullPointerException if the given value class is null
.
*/
public TextControl(final Class valueClass, final V defaultValue, final int rowCount, final int columnCount, final boolean lineWrap) {
this(new DefaultValueModel(valueClass, defaultValue), rowCount, columnCount, lineWrap); //construct the class with a default model
}
/**
* Value model, row count, and column count constructor with a default converter.
* @param valueModel The component value model.
* @param rowCount The requested number of visible rows, or -1 if no row count is specified.
* @param columnCount The requested number of visible columns, or -1 if no column count is specified.
* @throws NullPointerException if the given value model is null
.
*/
public TextControl(final ValueModel valueModel, final int rowCount, final int columnCount) {
this(valueModel, rowCount, columnCount, true); //default to line-wrapping
}
/**
* Value model, row count, column count, and line wrap constructor with a default converter.
* @param valueModel The component value model.
* @param rowCount The requested number of visible rows, or -1 if no row count is specified.
* @param columnCount The requested number of visible columns, or -1 if no column count is specified.
* @param lineWrap Whether lines should be wrapped in the view if needed.
* @throws NullPointerException if the given value model is null
.
*/
public TextControl(final ValueModel valueModel, final int rowCount, final int columnCount, final boolean lineWrap) {
this(valueModel, AbstractStringLiteralConverter.getInstance(valueModel.getValueClass()), rowCount, columnCount, lineWrap); //construct the class with a default converter
}
/**
* Value model constructor with a default converter.
* @param valueModel The component value model.
* @throws NullPointerException if the given value model is null
.
*/
public TextControl(final ValueModel valueModel) {
this(valueModel, AbstractStringLiteralConverter.getInstance(valueModel.getValueClass())); //construct the class with a default converter
}
/**
* Value model and converter constructor.
* @param valueModel The component value model.
* @param converter The converter for this component.
* @throws NullPointerException if the given value model and/or converter is null
.
*/
public TextControl(final ValueModel valueModel, final Converter converter) {
this(valueModel, converter, 1, -1, true); //construct the class with one row, defaulting to line wrap
}
/**
* Value model, converter, row count, column count, and line wrap constructor.
* @param valueModel The component value model.
* @param converter The converter for this component.
* @param rowCount The requested number of visible rows, or -1 if no row count is specified.
* @param columnCount The requested number of visible columns, or -1 if no column count is specified.
* @param lineWrap Whether lines should be wrapped in the view if needed.
* @throws NullPointerException if the given value model and/or converter is null
.
*/
public TextControl(final ValueModel valueModel, final Converter converter, final int rowCount, final int columnCount, final boolean lineWrap) {
this(valueModel, converter, rowCount, columnCount, lineWrap, rowCount > 1 && !lineWrap); //only turn on multiline initially if there are multiple rows and line wrap is off
}
/**
* Value model, converter, row count, column count, and line wrap constructor.
* @param valueModel The component value model.
* @param converter The converter for this component.
* @param rowCount The requested number of visible rows, or -1 if no row count is specified.
* @param columnCount The requested number of visible columns, or -1 if no column count is specified.
* @param lineWrap Whether lines should be wrapped in the view if needed.
* @param multiline Whether the user should be allowed to enter multiple physical lines if the control has multiple rows.
* @throws NullPointerException if the given value model and/or converter is null
.
*/
public TextControl(final ValueModel valueModel, final Converter converter, final int rowCount, final int columnCount, final boolean lineWrap,
final boolean multiline) {
super(valueModel, converter); //construct the parent class
this.rowCount = rowCount;
setColumnCount(columnCount);
this.lineWrap = lineWrap;
this.multiline = multiline;
addExportStrategy(DEFAULT_EXPORT_STRATEGY); //install a default export strategy
addImportStrategy(DEFAULT_IMPORT_STRATEGY); //install a default import strategy
}
/**
* The default transferable object for a text control.
* @author Garret Wilson
*/
protected static class DefaultTransferable extends AbstractTransferable> {
/**
* Source constructor.
* @param source The source of the transferable data.
* @throws NullPointerException if the provided source is null
.
*/
public DefaultTransferable(final TextControl source) {
super(source); //construct the parent class
}
/**
* {@inheritDoc}
*
* This implementation returns the text/plain
content type.
*
*/
@Override
public MediaType[] getContentTypes() {
return new MediaType[] { PLAIN_MEDIA_TYPE };
}
@Override
public Object transfer(final MediaType contentType) {
if(contentType.hasBaseType(PLAIN_MEDIA_TYPE)) { //if they request the supported content type
return getSource().getText(); //return the current text
} else { //if we don't support this content type
throw new IllegalArgumentException("Content type not supported: " + contentType);
}
}
}
}