All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codename1.components.SignatureComponent Maven / Gradle / Ivy

There is a newer version: 7.0.167
Show newest version
/*
 * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Codename One designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *  
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Please contact Codename One through http://www.codenameone.com/ if you 
 * need additional information or have any questions.
 */
package com.codename1.components;

import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Dialog;
import com.codename1.ui.Display;
import com.codename1.ui.Font;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.ImageFactory;
import com.codename1.ui.Stroke;
import com.codename1.ui.animations.Animation;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.events.ActionSource;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.geom.GeneralPath;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.GridLayout;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.EventDispatcher;


/**
 * A component to allow a user to enter their signature.  This is just a button that, when pressed,
 * will pop up a dialog where the user can draw their signature with their finger.  The user
 * is given the option to save/reset/cancel the signature.  On save, the {@link #signatureImage} property
 * will be set with a full-size image of the signature, and the "icon" on the button will show a thumbnail of
 * the image.
 * 
 * 

Example Usage

* * * *

Screenshots

* *

Signature Component button

*

Signature Component dialog

* *

Video Demo

* * * *

Source available here

. * * *

Styles

* *

You can customize the styles of various aspects of the Signature component using the following Styles (UIIDs) in * the theme:

* *
    *
  • {@literal SignatureButton} - The style for the main signature component button.
  • *
  • {@literal SignatureButtonBox} - A style to specify the "X" and "Box" that is drawn around the signature in the button.
  • *
  • {@literal SignaturePanel} - The panel that the user actually draws the signature in.
  • *
  • {@literal SignaturePanelBox} - The box and "X" in the SignaturePanel. Uses only the {@link Style#getFgColor() } property.
  • *
  • {@literal SignaturePanelSignature} - The signature that is drawn by the user. Uses only the {@link Style#getFgColor()} property.
  • *
* * @author shannah * @since 3.4 */ public class SignatureComponent extends Container implements ActionSource { private Image signatureImage; private final SignaturePanel signaturePanel = new SignaturePanel(); private final Button lead; private final EventDispatcher eventDispatcher = new EventDispatcher(); private final Font xFont; /** * We need to use a animation on this field to detect when the signature image changes * so that the icon can be correctly scaled to be shown on the button. */ private final Animation iconAnimation = new Animation() { @Override public boolean animate() { if (signatureImage != null && signatureImage.animate()) { Style s = getStyle(); Image icon = signatureImage; if (icon.getWidth() > getWidth() - s.getHorizontalPadding() || icon.getHeight() > getHeight() - s.getVerticalPadding()) { icon = signatureImage.scaledSmallerRatio( getWidth() - s.getHorizontalPadding(), getHeight() - s.getVerticalPadding() ); } lead.setIcon(icon); repaint(); return true; } return false; } @Override public void paint(Graphics g) { } }; /** * A hook that can be overridden by subclasses to be notified when the user * resets the signature in the signature dialog. * *

NOTE: Use of this hook to clear the current image in the signature * component is discouraged. The intention of the signature component's internal * dialog is to provide a staging area for the user to draw their signature. Changes * should only take effect in the app when the user commits their changes by pressing * "OK" or "Cancel". The "Reset" button in the signature dialog is intended to only * allow the user to fix a mistake and start over. Using this hook to cause * a change in application state (by, for example, calling {@link #setSignatureImage(com.codename1.ui.Image) } with * a {@literal null} argument}) may confuse the user.

* *

If you want to provide the user with a mechanism to "clear" the signature * from the signature component, you should add a button to your form which, when * pressed, will remove the image by calling {@link #setSignatureImage(com.codename1.ui.Image) }.

*/ protected void onSignatureReset() { } /** * Adds a listener to be notified when the signature image is changed. * @param l */ public void addActionListener(ActionListener l) { eventDispatcher.addListener(l); } /** * Removes a listener from being notified when signature image is changed. * @param l */ public void removeActionListener(ActionListener l) { eventDispatcher.removeListener(l); } /** * Fires an event to all listeners to notify them that the signature image has * been changed. */ protected void fireActionEvent() { eventDispatcher.fireActionEvent(new ActionEvent(this)); } /** * Overridden to register the icon animation when the field is added to the form. */ @Override protected void initComponent() { super.initComponent(); getComponentForm().registerAnimated(iconAnimation); } /** * Overridden to deregister the icon animation when the field is removed from the form. */ @Override protected void deinitialize() { getComponentForm().deregisterAnimated(iconAnimation); super.deinitialize(); } private String localize(String key, String defaultVal) { return UIManager.getInstance().localize(key, defaultVal); } /** * Creates a new signature component. */ public SignatureComponent() { setLayout(new BorderLayout()); xFont = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_LARGE); final Style signatureButtonBoxStyle = getUIManager().getComponentStyle("SignatureButtonBox"); lead = new Button() { @Override protected void paintBackground(Graphics g) { super.paintBackground(g); g.setColor(signatureButtonBoxStyle.getFgColor()); int alpha = g.concatenateAlpha(signatureButtonBoxStyle.getFgAlpha()); Style s = getStyle(); g.drawRect( getX()+s.getPaddingLeftNoRTL(), getY()+s.getPaddingTop(), getWidth()-s.getPaddingRightNoRTL()-s.getPaddingLeftNoRTL(), getHeight()-s.getPaddingBottom() - s.getPaddingTop() ); g.setFont(xFont); g.drawString("X", getX() + getStyle().getPaddingLeftNoRTL() + Display.getInstance().convertToPixels(3, true), getY() + getHeight() / 2); g.setAlpha(alpha); } }; lead.setText(localize("SignatureComponent.LeadText","Press to sign")); lead.setUIID("SignatureButton"); lead.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { final Dialog dialog = new Dialog(localize("SignatureComponent.DialogTitle", "Sign Here")); final SignatureDialogBody sigBody = new SignatureDialogBody() { @Override protected void onCancel() { super.onCancel(); dialog.dispose(); } }; signaturePanel.clear(); sigBody.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent sigDoneEvent) { dialog.dispose(); setSignatureImage(sigBody.getValue()); fireActionEvent(); } }); dialog.setLayout(new BorderLayout()); dialog.addComponent(BorderLayout.CENTER, sigBody); dialog.setScrollable(false); dialog.setFocusable(false); dialog.show(); } }); addComponent(BorderLayout.CENTER, lead); } /** * Calculates the preferred size of the button itself (not the signature canvas... but the button you press to show the signature panel). * @return */ @Override protected Dimension calcPreferredSize() { int w = Display.getInstance().convertToPixels(75, true); int h = Display.getInstance().convertToPixels(40, true); return new Dimension(w, h); } /** * Sets the signature image for this field. This will also scale the image and * show it as the icon for the button. * @param img The image to set as the signature image. */ public void setSignatureImage(Image img) { if (img != signatureImage) { signatureImage = img; lead.setText(""); if (img != null) { int maxW = lead.getWidth() - lead.getStyle().getPaddingLeftNoRTL() - lead.getStyle().getPaddingRightNoRTL(); int maxH = lead.getHeight() - lead.getStyle().getPaddingTop()- lead.getStyle().getPaddingBottom(); Image icon = img; if ((icon.getWidth() > maxW || icon.getHeight() > maxH) && maxW > 1 && maxH > 1) { icon = icon.scaledSmallerRatio(maxW, maxH); } lead.setIcon(icon); } else { lead.setText(localize("SignatureComponent.LeadText","Press to sign")); lead.setIcon(null); } } } /** * Gets the image of the signature - or null if no signature has been drawn. * @return */ public Image getSignatureImage() { if(signatureImage != null){ return signatureImage; }else{ return signaturePanel.getImage(); } } /** * Get the component that is the actual panel for drawing a signature. * The component can be used instead of the SignatureComponent if an embedded signature is needed. * * Use the clearSignaturePanel() and the getSignatureImage() functions to work with this component. * * @return */ public Component getSignaturePanel(){ return signaturePanel; } /** * Clear the signature image and allow it to draw a new one. * Use only if you use the signature panel component from the getSignaturePanel() method. */ public void clearSignaturePanel(){ signaturePanel.clear(); } /** * Inner class with the actual body of the dialog for drawing the signature. This dialog * is shown when the user clicks on the main button. * @author shannah */ private class SignatureDialogBody extends Container { private Button doneButton; private Button resetButton; private Button cancelButton; private final EventDispatcher eventDispatcher = new EventDispatcher(); private Image value; public SignatureDialogBody() { setLayout(new BorderLayout()); addComponent(BorderLayout.CENTER, signaturePanel); doneButton = new Button( localize("SignatureComponent.SaveButtonLabel", "Save"), getUIManager().getThemeConstant("sigButtonOKUIID", "Button")); resetButton = new Button( localize("SignatureComponent.ResetButtonLabel", "Reset"), getUIManager().getThemeConstant("sigButtonResetUIID", "Button")); cancelButton = new Button( localize("SignatureComponent.CancelButtonLabel", "Cancel"), getUIManager().getThemeConstant("sigButtonCancelUIID", "Button")); doneButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { value = signaturePanel.getImage(); if (value == null) { Dialog.show( localize("SignatureComponent.ErrorDialog.SignatureRequired.Title", "Signature Required"), localize("SignatureComponent.ErrorDialog.SignatureRequired.Body", "Please draw your signature in the space provided."), localize("SignatureComponent.ErrorDialog.OK", "OK"), null ); return; } eventDispatcher.fireActionEvent(new ActionEvent(this)); removeComponent(signaturePanel); } }); resetButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { signaturePanel.clear(); onSignatureReset(); repaint(); } }); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { removeComponent(signaturePanel); onCancel(); } }); addComponent(BorderLayout.SOUTH, GridLayout.encloseIn(3, cancelButton, resetButton, doneButton)); } /** * Called when the cancel button is pressed. Should be overridden by subclasses to * actually do something (like close the dialog). */ protected void onCancel() { } /** * Adds a listener to be informed when the "done" button is pressed and a new * signature has been saved as an image. * @param l */ public void addActionListener(ActionListener l) { eventDispatcher.addListener(l); } /** * @see #addActionListener(com.codename1.ui.events.ActionListener) * @param l */ public void removeActionListener(ActionListener l) { eventDispatcher.removeListener(l); } /** * Overridden to automatically save the image of the current SignaturePanel * when the dialog is disposed. */ @Override protected void deinitialize() { if (value == null) { // We want to cache the value when this field is being hidden. value = signaturePanel.getImage(); } super.deinitialize(); } /** * Gets the signature that was drawn, as an Image. * @return */ public Image getValue() { if (value == null) { value = signaturePanel.getImage(); } return value; } } /** * The actual panel for drawing a signature. This doesn't include any buttons (like done, reset, or cancel), * it merely provides the functionality to record the drawing of a signature. */ private class SignaturePanel extends Component { private final GeneralPath path = new GeneralPath(); private final Stroke stroke = new Stroke(); private final Rectangle signatureRect = new Rectangle(); private boolean initialized; private Style signatureBoxStyle; private Style signatureStyle; SignaturePanel() { setUIID("SignaturePanel"); signatureBoxStyle = getUIManager().getComponentStyle("SignaturePanelBox"); signatureStyle = getUIManager().getComponentStyle("SignaturePanelSignature"); stroke.setLineWidth(Math.max(1, Display.getInstance().convertToPixels(1, true)/2)); } /** * Overridden to try to make this component as sensitive as possible to * drag events. If we don't do this, it requires a longer drag before the "drag" * events will kick in. * @param x * @param y * @return */ @Override protected int getDragRegionStatus(int x, int y) { return Component.DRAG_REGION_LIKELY_DRAG_XY; } /** * * @param g */ @Override public void paint(Graphics g) { super.paint(g); g.setColor(signatureBoxStyle.getFgColor()); int alpha = g.concatenateAlpha(signatureBoxStyle.getFgAlpha()); calcSignatureRect(signatureRect); g.drawRect(signatureRect.getX(), signatureRect.getY(), signatureRect.getWidth(), signatureRect.getHeight()); g.drawString("X", signatureRect.getX() + Display.getInstance().convertToPixels(1, true), signatureRect.getY() + signatureRect.getHeight() / 2); g.pushClip(); g.clipRect(signatureRect.getX(), signatureRect.getY(), signatureRect.getWidth(), signatureRect.getHeight()); paintSignature(g); g.popClip(); g.setAlpha(alpha); } /** * Paints just the signature portion of the panel. This is is reuised to * also create the image of the signature. * @param g */ private void paintSignature(Graphics g) { g.setColor(signatureStyle.getFgColor()); int alpha = g.concatenateAlpha(signatureStyle.getFgAlpha()); boolean oldAA = g.isAntiAliased(); g.setAntiAliased(true); g.drawShape(path, stroke); g.setAntiAliased(oldAA); g.setAlpha(alpha); } /** * Calculates a rectangle (in parent component space) used for the drawn "rectangle" inside * which the user should draw their signature. It tries to create a 16x9 rectangle that * fits inside the component with a bit of padding (3mm on each edge). * @param r Output variable. */ private void calcSignatureRect(Rectangle r) { int w = getWidth() - Display.getInstance().convertToPixels(6, true); int h = (int)(w * 9.0 / 16.0); if (h > getHeight()) { h = getHeight() - Display.getInstance().convertToPixels(6, false); w = (int)(h * 16.0 / 9.0); } r.setX(getX() + (getWidth() - w) / 2); r.setY(getY() + (getHeight() - h)/2); r.setWidth(w); r.setHeight(h); } @Override protected Dimension calcPreferredSize() { Display d = Display.getInstance(); return new Dimension(d.convertToPixels(80, true), d.convertToPixels(40, false)); } @Override public void pointerPressed(int x, int y) { path.moveTo(x(x), y(y)); initialized = true; repaint(); } @Override public void pointerDragged(int x, int y) { if(!initialized) { initialized = true; path.moveTo(x(x), y(y)); } else { path.lineTo(x(x), y(y)); } repaint(); } @Override public void pointerReleased(int x, int y) { path.lineTo(x(x), y(y)); repaint(); } /** * Converts an x coordinate from screen space, to parent component space. * @param x * @return */ private int x(int x) { return x - getParent().getAbsoluteX(); } /** * Converts a y coordinate from screen space to parent component space. * @param y * @return */ private int y(int y) { return y - getParent().getAbsoluteY(); } /** * Gets the currently drawn signature as an image. This only includes the * areas inside the {@link #signatureRect} * @return */ public Image getImage() { if(path.getPointsSize() < 2){ return null; } calcSignatureRect(signatureRect); Image img = ImageFactory.createImage(this, signatureRect.getWidth(), signatureRect.getHeight(), 0xffffff); Graphics g = img.getGraphics(); g.translate(-signatureRect.getX(), -signatureRect.getY()); paintSignature(g); return img; } /** * Resets the signature as a blank path. */ public void clear() { path.reset(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy