/*
* Copyright 2014 Max Schuster
*
* 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 eu.maxschuster.vaadin.signaturefield;
import com.vaadin.data.Property;
import com.vaadin.data.Validator;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomField;
import com.vaadin.ui.Field;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import eu.maxschuster.dataurl.DataUrl;
import eu.maxschuster.vaadin.signaturefield.converter.StringToDataUrlConverter;
import eu.maxschuster.vaadin.signaturefield.shared.MimeType;
import java.util.Collection;
import java.util.logging.Logger;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;
/**
* A {@link Field} to capture user signatures as data url {@link String}.<br>
* <br>
* If you need extended access to the data urls content you can use the
* {@link StringToDataUrlConverter} that converts the String value to
* {@link DataUrl} that allows access to the binary contents of the data url.
* <br>
* <br>
* <a href="https://github.com/szimek/signature_pad">signature_pad</a>
* by Szymon Nowak (<a href="https://github.com/szimek">szimek</a>) is used to
* capture the signature at the client-side.
*
* @author Max Schuster
* @see StringToDataUrlConverter
* @see DataUrl
* @see <a href="https://github.com/szimek/signature_pad">signature_pad</a>
*/
public class SignatureField extends CustomField<String> {
private static final long serialVersionUID = 1L;
/**
* The extension instance
*/
private final SignatureFieldExtension extension;
/**
* True if client-side is updating the signature
*/
private boolean changingVariables = false;
/**
* Creates a new SignatureField instance
*/
public SignatureField() {
this(null, null);
}
/**
* Creates a new SignatureField instance with a caption
*
* @param caption
* Field caption
*/
public SignatureField(String caption) {
this(caption, null);
}
/**
* Creates a new SignatureField instance with a data source
*
* @param dataSource
* Property data source
*/
public SignatureField(Property<?> dataSource) {
this(null, dataSource);
}
/**
* Creates a new SignatureField instance with a caption and data source
*
* @param caption
* Field caption
* @param dataSource
* Property data source
*/
public SignatureField(String caption, Property<?> dataSource) {
super();
extension = initExtension();
setImmediate(false);
setHeight(100, Unit.PIXELS);
setWidth(300, Unit.PIXELS);
setCaption(caption);
setPropertyDataSource(dataSource);
setPrimaryStyleName("signaturefield");
}
@Override
public Class<? extends String> getType() {
return String.class;
}
/**
* Allways returns <code>null</code>
* @return Allways <code>null</code>
*/
@Override
protected final Component initContent() {
return null;
}
/**
* Allways returns <code>null</code>
* @return Allways <code>null</code>
*/
@Override
protected Component getContent() {
return null;
}
/**
* Creates the javascript extension used to communicate with the
* client-side.
* @return The extension of this field
*/
private SignatureFieldExtension initExtension() {
SignatureFieldExtension ext = new SignatureFieldExtension(this);
ext.addSignatureChangeListener(new SignatureFieldExtension.SignatureChangeListener() {
private static final long serialVersionUID = 1L;
@Override
public void signatureChange(SignatureFieldExtension.SignatureChangeEvent event) {
changingVariables = true;
try {
setValue(event.getSignature(), true);
} finally {
changingVariables = false;
}
}
});
return ext;
}
@Override
public void setReadOnly(boolean readOnly) {
super.setReadOnly(readOnly);
extension.setReadOnly(readOnly);
}
@Override
public void setImmediate(boolean immediate) {
super.setImmediate(immediate);
extension.setImmediate(immediate);
}
/**
* Is the field empty?<br>
* The field is considered empty if its value
* is {@code null}
*
* @return Is the field empty?
*/
@Override
public boolean isEmpty() {
return super.isEmpty();
}
/**
* Sets the internal field value. Sends the value to the client-side.
*
* @param newValue
* the new value to be set.
*/
@Override
protected void setInternalValue(String newValue) {
setInternalValue(newValue, false);
}
/**
* Sets the internal field value. May sends the value to the client-side.
*
* @param newValue
* the new value to be set.
* @param repaintIsNotNeeded
* the new value should not be send to the client-side
*/
protected void setInternalValue(String newValue, boolean repaintIsNotNeeded) {
super.setInternalValue(newValue);
extension.setSignature(newValue, changingVariables || repaintIsNotNeeded);
}
/**
* Clears the field.
*
* @see #setValue(java.lang.Object)
*/
@Override
public void clear() {
setValue(null);
extension.clear();
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Element,
* com.vaadin.ui.declarative.DesignContext)
*/
@Override
public void readDesign(Element design, DesignContext designContext) {
super.readDesign(design, designContext);
Attributes attr = design.attributes();
if (attr.hasKey("mime-type")) {
MimeType mimeType = null;
String mimeTypeString = DesignAttributeHandler.getFormatter().parse(
attr.get("mime-type"), String.class);
try {
mimeType = MimeType.valueOfMimeType(mimeTypeString);
} catch (IllegalArgumentException e) {
Logger.getLogger(SignatureField.class.getName()).info(
"Unsupported MIME-Type found when reading from design : "
.concat(mimeTypeString));
}
setMimeType(mimeType);
}
}
@Override
public void writeDesign(Element design, DesignContext designContext) {
super.writeDesign(design, designContext);
Attributes attr = design.attributes();
SignatureField def = designContext.getDefaultInstance(this);
MimeType mimeType = getMimeType();
if (mimeType != null) {
String mimeTypeDef = null;
if (def.getMimeType() != null) {
mimeTypeDef = getMimeType().getMimeType();
}
DesignAttributeHandler.writeAttribute("mime-type", attr,
mimeType.getMimeType(), mimeTypeDef, String.class);
}
}
@Override
protected Collection<String> getCustomAttributes() {
Collection<String> a = super.getCustomAttributes();
a.add("mime-type");
return a;
}
/**
* Gets the radius of a single dot.
*
* @return Radius of a single dot.
*/
public Double getDotSize() {
return extension.getDotSize();
}
/**
* Sets the radius of a single dot.
*
* @param dotSize Radius of a single dot.
*/
public void setDotSize(Double dotSize) {
extension.setDotSize(dotSize);
}
/**
* Sets the radius of a single dot.
*
* @param dotSize Radius of a single dot.
* @return This {@link SignatureField}
*/
public SignatureField withDotSize(Double dotSize) {
extension.setDotSize(dotSize);
return this;
}
/**
* Gets the minimum width of a line. Defaults to 0.5.
*
* @return Minimum width of a line.
*/
public double getMinWidth() {
return extension.getMinWidth();
}
/**
* Sets the minimum width of a line. Defaults to 0.5.
*
* @param minWidth Minimum width of a line.
*/
public void setMinWidth(double minWidth) {
extension.setMinWidth(minWidth);
}
/**
* Sets the minimum width of a line. Defaults to 0.5.
*
* @param minWidth Minimum width of a line.
* @return This {@link SignatureField}
*/
public SignatureField withMinWidth(double minWidth) {
setMinWidth(minWidth);
return this;
}
/**
* Gets the maximum width of a line.
*
* @return Maximum width of a line.
*/
public double getMaxWidth() {
return extension.getMaxWidth();
}
/**
* Sets the maximum width of a line.
*
* @param maxWidth Maximum width of a line.
*/
public void setMaxWidth(double maxWidth) {
extension.setMaxWidth(maxWidth);
}
/**
* Sets the maximum width of a line.
*
* @param maxWidth Maximum width of a line.
* @return This {@link SignatureField}
*/
public SignatureField withMaxWidth(double maxWidth) {
setMaxWidth(maxWidth);
return this;
}
/**
* Gets the color used to clear the background. Can be any color format
* accepted by context.fillStyle. Defaults to "rgba(0,0,0,0)" (transparent
* black). Use a non-transparent color e.g. "rgb(255,255,255)" (opaque
* white) if you'd like to save signatures as JPEG images.<br>
* <br>
* Some predefined colors can be found in class {@link SampleColors}
*
* @return Color used to clear the background.
*/
public String getBackgroundColor() {
return extension.getBackgroundColor();
}
/**
* Sets the color used to clear the background. Can be any color format
* accepted by context.fillStyle. Defaults to "rgba(0,0,0,0)" (transparent
* black). Use a non-transparent color e.g. "rgb(255,255,255)" (opaque
* white) if you'd like to save signatures as JPEG images.<br>
* <br>
* Some predefined colors can be found in class {@link SampleColors}
*
* @param backgroundColor Color used to clear the background.
*/
public void setBackgroundColor(String backgroundColor) {
extension.setBackgroundColor(backgroundColor);
}
/**
* Sets the color used to clear the background. Can be any color format
* accepted by context.fillStyle. Defaults to "rgba(0,0,0,0)" (transparent
* black). Use a non-transparent color e.g. "rgb(255,255,255)" (opaque
* white) if you'd like to save signatures as JPEG images.<br>
* <br>
* Some predefined colors can be found in class {@link SampleColors}
*
* @param backgroundColor Color used to clear the background.
* @return This {@link SignatureField}
*/
public SignatureField withBackgroundColor(String backgroundColor) {
setBackgroundColor(backgroundColor);
return this;
}
/**
* Sets the color used to draw the lines. Can be any color format accepted
* by context.fillStyle.<br>
* <br>
* Some predefined colors can be found in class {@link SampleColors}
*
* @return The color used to draw the lines.
*/
public String getPenColor() {
return extension.getPenColor();
}
/**
* Sets the color used to draw the lines. Can be any color format accepted
* by context.fillStyle.<br>
* <br>
* Some predefined colors can be found in class {@link SampleColors}
*
* @param penColor The color used to draw the lines.
*/
public void setPenColor(String penColor) {
extension.setPenColor(penColor);
}
/**
* Sets the color used to draw the lines. Can be any color format accepted
* by context.fillStyle.<br>
* <br>
* Some predefined colors can be found in class {@link SampleColors}
*
* @param penColor The color used to draw the lines.
* @return This {@link SignatureField}
*/
public SignatureField withPenColor(String penColor) {
setPenColor(penColor);
return this;
}
/**
* Gets the velocity filter weight
*
* @return The velocity filter weight
*/
public double getVelocityFilterWeight() {
return extension.getVelocityFilterWeight();
}
/**
* Sets the velocity filter weight
*
* @param velocityFilterWeight The velocity filter weight
*/
public void setVelocityFilterWeight(double velocityFilterWeight) {
extension.setVelocityFilterWeight(velocityFilterWeight);
}
/**
* Sets the velocity filter weight
*
* @param velocityFilterWeight The velocity filter weight
* @return This {@link SignatureField}
*/
public SignatureField withVelocityFilterWeight(double velocityFilterWeight) {
setVelocityFilterWeight(velocityFilterWeight);
return this;
}
/**
* Sets the {@link MimeType} of generated images
*
* @return The {@link MimeType} of generated images
*/
public MimeType getMimeType() {
return extension.getMimeType();
}
/**
* Sets the {@link MimeType} of generated images
*
* @param mimeType The {@link MimeType} of generated images
*/
public void setMimeType(MimeType mimeType) {
extension.setMimeType(mimeType);
}
/**
* Sets the {@link MimeType} of generated images
*
* @param mimeType The {@link MimeType} of generated images
* @return This {@link SignatureField}
*/
public SignatureField withMimeType(MimeType mimeType) {
setMimeType(mimeType);
return this;
}
/**
* Gets the visibility of the clear button
*
* @return Should show a clear button in the {@link SignatureField}
*/
public boolean isClearButtonEnabled() {
return extension.isClearButtonEnabled();
}
/**
* Sets the visibility of the clear button
*
* @param clearButtonEnabled Should show a clear button in the
* {@link SignatureField}
*/
public void setClearButtonEnabled(boolean clearButtonEnabled) {
extension.setClearButtonEnabled(clearButtonEnabled);
}
/**
* Sets the visibility of the clear button
*
* @param clearButtonEnabled Should show a clear button in the
* {@link SignatureField}
* @return This {@link SignatureField}
*/
public SignatureField withClearButtonEnabled(boolean clearButtonEnabled) {
setClearButtonEnabled(clearButtonEnabled);
return this;
}
/**
* Sets the error that is shown if the field value cannot be converted to
* the data source type. If {0} is present in the message, it will be
* replaced by the simple name of the data source type. If {1} is present in
* the message, it will be replaced by the ConversionException message.
*
* @param valueConversionError Message to be shown when conversion of the
* value fails
* @return This {@link SignatureField}
* @see #setConversionError(java.lang.String)
* @see #setImmediate(boolean)
*/
public SignatureField withConversionError(String valueConversionError) {
setImmediate(true);
setConversionError(valueConversionError);
return this;
}
/**
* Sets the converter used to convert the field value to property data
* source type. The converter must have a presentation type that matches the
* field type.
*
* @param converter The new converter to use.
* @return This {@link SignatureField}
* @see #setConverter(com.vaadin.data.util.converter.Converter)
* @see #setImmediate(boolean)
*/
public SignatureField withConverter(Converter<String, ?> converter) {
setImmediate(true);
setConverter(converter);
return this;
}
/**
* Sets the width of the object to "100%".
* @return This {@link SignatureField}
* @see #setWidth(float, com.vaadin.server.Sizeable.Unit)
*/
public SignatureField withFullWidth() {
setWidth(100f, Unit.PERCENTAGE);
return this;
}
/**
* Sets the height of the object to "100%".
* @return This {@link SignatureField}
* @see #setHeight(float, com.vaadin.server.Sizeable.Unit)
*/
public SignatureField withFullHeight() {
setHeight(100f, Unit.PERCENTAGE);
return this;
}
/**
* Changes the readonly state and throw read-only status change events.
*
* @param readOnly a boolean value specifying whether the component is put
* read-only mode or not
* @return This {@link SignatureField}
* @see #setReadOnly(boolean)
*/
public SignatureField withReadOnly(boolean readOnly) {
setReadOnly(readOnly);
return this;
}
/**
* Adds a new validator for the field's value. All validators added to a
* field are checked each time the its value changes.
*
* @param validator the new validator to be added.
* @return This {@link SignatureField}
* @see #addValidator(com.vaadin.data.Validator)
*/
public SignatureField withValidator(Validator validator) {
setImmediate(true);
addValidator(validator);
return this;
}
/**
* Sets the width of the object. Negative number implies unspecified size
* (terminal is free to set the size).
*
* @param width the width of the object.
* @param unit the unit used for the width.
* @return This {@link SignatureField}
* @see #setWidth(float, com.vaadin.server.Sizeable.Unit)
*/
public SignatureField withWidth(float width, Unit unit) {
setWidth(width, unit);
return this;
}
/**
* Sets the width of the component using String presentation.
*
* @param width in CSS style string representation, null or empty string to
* reset
* @return This {@link SignatureField}
* @see #setWidth(java.lang.String)
*/
public SignatureField withWidth(String width) {
setWidth(width);
return this;
}
/**
* Sets the height of the object. Negative number implies unspecified size
* (terminal is free to set the size).
*
* @param height the height of the object.
* @param unit the unit used for the width.
* @return This {@link SignatureField}
* @see #setHeight(float, com.vaadin.server.Sizeable.Unit)
*/
public SignatureField withHeight(float height, Unit unit) {
setHeight(height, unit);
return this;
}
/**
* Sets the height of the component using String presentation.
*
* @param height Height of the component
* @return This {@link SignatureField}
* @see #setHeight(java.lang.String)
*/
public SignatureField withHeight(String height) {
setHeight(height);
return this;
}
}