/******************************************************************************
* Product: Posterita Ajax UI *
* Copyright (C) 2007 Posterita Ltd. All Rights Reserved. *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. This program 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 for more details. *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
* For the text or an alternative of this public license, you may reach us *
* Posterita Ltd., 3, Draper Avenue, Quatre Bornes, Mauritius *
* or via info@posterita.org or http://www.posterita.org/ *
*****************************************************************************/
package org.adempiere.webui.component;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.text.ParseException;
import org.adempiere.webui.ClientInfo;
import org.adempiere.webui.LayoutUtils;
import org.adempiere.webui.theme.ThemeManager;
import org.adempiere.webui.util.ZKUpdateUtil;
import org.compiere.model.MSysConfig;
import org.compiere.util.DisplayType;
import org.compiere.util.Env;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Decimalbox;
import org.zkoss.zul.Div;
import org.zkoss.zul.Hbox;
import org.zkoss.zul.Popup;
import org.zkoss.zul.Vbox;
/**
* Composite component of {@link Decimalbox} and {@link Button}
* @author Ashley G Ramdass
* @date Mar 11, 2007
* @version $Revision: 0.10 $
*
* @author Low Heng Sin
*/
public class NumberBox extends Div
{
/**
* generated serial id
*/
private static final long serialVersionUID = 8543853599051754172L;
/** Text box for calculator */
private Textbox txtCalc = new Textbox();
/** true for integer, false for number with decimal point */
protected boolean integral = false;
protected NumberFormat format = null;
private Decimalbox decimalBox = null;
private Button btn;
/** calculator popup */
private Popup popup;
/**
* @param integral
*/
public NumberBox(boolean integral)
{
this(integral, false);
}
/**
* @param integral
* @param tableEditor
*/
public NumberBox(boolean integral, boolean tableEditor)
{
super();
this.integral = integral;
init(tableEditor);
}
/**
* Layout component
* @param tableEditor
*/
private void init(boolean tableEditor)
{
decimalBox = new Decimalbox();
if (integral)
decimalBox.setScale(0);
decimalBox.setStyle("display: inline-block;text-align:right");
ZKUpdateUtil.setHflex(decimalBox, "0");
decimalBox.setSclass("editor-input");
decimalBox.setId(decimalBox.getUuid());
char separatorChar = DisplayType.getNumberFormat(DisplayType.Number, null).getDecimalFormatSymbols().getDecimalSeparator();
String separator = Character.toString(separatorChar);
boolean processDotKeypad = MSysConfig.getBooleanValue(MSysConfig.ZK_DECIMALBOX_PROCESS_DOTKEYPAD, true, Env.getAD_Client_ID(Env.getCtx()));
if (processDotKeypad && ! ".".equals(separator)) {
/* this code works for the decimalbox - the calculator is managed in calc.js */
StringBuffer funct = new StringBuffer();
funct.append("function(evt)");
funct.append("{");
// ignore dot, comma and decimal separator and process them on key down
funct.append(" if (!this._shallIgnore(evt, '0123456789-%'))");
funct.append(" {");
funct.append(" this.$doKeyPress_(evt);");
funct.append(" }");
funct.append("}");
decimalBox.setWidgetOverride("doKeyPress_", funct.toString());
funct = new StringBuffer();
// debug // funct.append("console.log('keyCode='+event.keyCode);");
funct.append("(function(event) {");
funct.append("let key=0;");
funct.append("if (window.event)");
funct.append(" key = event.keyCode;");
funct.append("else");
funct.append(" key = event.which;");
funct.append("if (key == 108 || key == 110 || key == 188 || key == 190 || key == 194) {");
funct.append(" let id = '$'.concat('").append(decimalBox.getId()).append("');");
funct.append(" let calcText = jq(id)[0];");
funct.append(" let position = calcText.selectionStart;");
funct.append(" let newValue = calcText.value.substring(0, position) + '").append(separator).append("' + calcText.value.substring(position);");
funct.append(" calcText.value = newValue;");
funct.append(" calcText.setSelectionRange(position+1, position+1);");
funct.append(" event.stop;");
funct.append("}})(event);");
decimalBox.setWidgetListener("onKeyDown", funct.toString());
}
appendChild(decimalBox);
btn = new Button();
if (ThemeManager.isUseFontIconForImage())
btn.setIconSclass("z-icon-Calculator");
else
btn.setImage(ThemeManager.getThemeResource("images/Calculator16.png"));
btn.setTabindex(-1);
ZKUpdateUtil.setHflex(btn, "0");
btn.addEventListener(Events.ON_CLICK, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
if (popup != null) {
popup.open(NumberBox.this, "after_start");
// Fill the calculator with the actual value of the field
// TODO: this could be made a user preference
String curValue = "";
if (decimalBox.getValue() != null) {
curValue = decimalBox.getValue().toString();
boolean processDotKeypad = MSysConfig.getBooleanValue(MSysConfig.ZK_DECIMALBOX_PROCESS_DOTKEYPAD, true, Env.getAD_Client_ID(Env.getCtx()));
if (processDotKeypad) {
char separatorChar = DisplayType.getNumberFormat(DisplayType.Number, null).getDecimalFormatSymbols().getDecimalSeparator();
String separator = Character.toString(separatorChar);
curValue = curValue.replace(".", separator);
}
if ("0".equals(curValue)) {
curValue = "";
}
}
String txtCalcId = txtCalc.getId();
Clients.evalJavaScript("calc.append('" + txtCalcId + "', '" + curValue + "')");
}
}
});
LayoutUtils.addSclass("editor-button", btn);
appendChild(btn);
popup = getCalculatorPopup();
appendChild(popup);
btn.setStyle("text-align: center;");
LayoutUtils.addSclass("number-box", this);
LayoutUtils.addSclass("editor-box", this);
if (ClientInfo.isMobile())
{
LayoutUtils.addSclass("mobile", decimalBox);
btn.setVisible(false);
}
}
/**
* Set number format
* @param format
*/
public void setFormat(NumberFormat format)
{
this.format = format;
}
/**
* Set value to {@link #decimalBox}
* @param value
*/
public void setValue(Object value)
{
if (value == null)
decimalBox.setValue((BigDecimal) null);
else if (value instanceof BigDecimal)
decimalBox.setValue((BigDecimal) value);
else if (value instanceof Number)
decimalBox.setValue(BigDecimal.valueOf(((Number)value).doubleValue()));
else
decimalBox.setValue(new BigDecimal(value.toString()));
}
/**
* Get value from {@link #decimalBox}
* @return BigDecimal
*/
public BigDecimal getValue()
{
return decimalBox.getValue();
}
/**
* Get text from {@link #decimalBox}
* @return text
*/
public String getText()
{
BigDecimal value = decimalBox.getValue();
if (value == null) return null;
return decimalBox.getText();
}
/**
* Set value to {@link #decimalBox}
* @param value
*/
public void setValue(String value)
{
Number numberValue = null;
if (format != null)
{
try
{
numberValue = format.parse(value);
setValue(numberValue);
}
catch (ParseException e)
{
}
}
else
{
decimalBox.setValue(new BigDecimal(value));
}
}
/**
* Create calculator popup
* @return Popup
*/
private Popup getCalculatorPopup()
{
Popup popup = new Popup();
Vbox vbox = new Vbox();
char separatorChar = DisplayType.getNumberFormat(DisplayType.Number, null).getDecimalFormatSymbols().getDecimalSeparator();
String separator = Character.toString(separatorChar);
txtCalc = new Textbox();
decimalBox.setId(decimalBox.getUuid());
txtCalc.setId(txtCalc.getUuid());
boolean processDotKeypad = MSysConfig.getBooleanValue(MSysConfig.ZK_DECIMALBOX_PROCESS_DOTKEYPAD, true, Env.getAD_Client_ID(Env.getCtx()));
StringBuffer funct = new StringBuffer();
funct.append("function(evt)");
funct.append("{");
if (processDotKeypad) {
funct.append(" if (!this._shallIgnore(evt, '= -/()*%+0123456789'))");
} else {
// restrict allowed characters
String decimalSep = separator;
if (!".".equals(separator))
decimalSep += ".";
funct.append(" if (!this._shallIgnore(evt, '= -/()*%+0123456789").append(decimalSep).append("'))");
}
funct.append(" {");
funct.append(" this.$doKeyPress_(evt);");
funct.append(" }");
funct.append("}");
txtCalc.setWidgetOverride("doKeyPress_", funct.toString());
txtCalc.setWidgetListener("onKeyDown", "calc.validateDown('" +
decimalBox.getId() + "','" + txtCalc.getId()
+ "'," + integral + "," + (int)separatorChar + ", event, " + ( processDotKeypad ? "true" : "false" ) + ");");
txtCalc.setMaxlength(250);
txtCalc.setCols(30);
String txtCalcId = txtCalc.getId();
vbox.appendChild(txtCalc);
Hbox row1 = new Hbox();
Button btnAC = new Button();
ZKUpdateUtil.setWidth(btnAC, "40px");
btnAC.setLabel("AC");
btnAC.setWidgetListener("onClick", "calc.clearAll('" + txtCalcId + "')");
Button btn7 = new Button();
ZKUpdateUtil.setWidth(btn7, "30px");
btn7.setLabel("7");
btn7.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', '7')");
Button btn8 = new Button();
ZKUpdateUtil.setWidth(btn8, "30px");
btn8.setLabel("8");
btn8.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', '8')");
Button btn9 = new Button();
ZKUpdateUtil.setWidth(btn9, "30px");
btn9.setLabel("9");
btn9.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', '9')");
Button btnMultiply = new Button();
ZKUpdateUtil.setWidth(btnMultiply, "30px");
btnMultiply.setLabel("*");
btnMultiply.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', ' * ')");
row1.appendChild(btnAC);
row1.appendChild(btn7);
row1.appendChild(btn8);
row1.appendChild(btn9);
row1.appendChild(btnMultiply);
Hbox row2 = new Hbox();
Button btnC = new Button();
ZKUpdateUtil.setWidth(btnC, "40px");
btnC.setLabel("C");
btnC.setWidgetListener("onClick", "calc.clear('" + txtCalcId + "')");
Button btn4 = new Button();
ZKUpdateUtil.setWidth(btn4, "30px");
btn4.setLabel("4");
btn4.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', '4')");
Button btn5 = new Button();
ZKUpdateUtil.setWidth(btn5, "30px");
btn5.setLabel("5");
btn5.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', '5')");
Button btn6 = new Button();
ZKUpdateUtil.setWidth(btn6, "30px");
btn6.setLabel("6");
btn6.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', '6')");
Button btnDivide = new Button();
ZKUpdateUtil.setWidth(btnDivide, "30px");
btnDivide.setLabel("/");
btnDivide.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', ' / ')");
row2.appendChild(btnC);
row2.appendChild(btn4);
row2.appendChild(btn5);
row2.appendChild(btn6);
row2.appendChild(btnDivide);
Hbox row3 = new Hbox();
Button btnModulo = new Button();
ZKUpdateUtil.setWidth(btnModulo, "40px");
btnModulo.setLabel("%");
btnModulo.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', ' % ')");
Button btn1 = new Button();
ZKUpdateUtil.setWidth(btn1, "30px");
btn1.setLabel("1");
btn1.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', '1')");
Button btn2 = new Button();
ZKUpdateUtil.setWidth(btn2, "30px");
btn2.setLabel("2");
btn2.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', '2')");
Button btn3 = new Button();
ZKUpdateUtil.setWidth(btn3, "30px");
btn3.setLabel("3");
btn3.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', '3')");
Button btnSubstract = new Button();
ZKUpdateUtil.setWidth(btnSubstract, "30px");
btnSubstract.setLabel("-");
btnSubstract.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', ' - ')");
row3.appendChild(btnModulo);
row3.appendChild(btn1);
row3.appendChild(btn2);
row3.appendChild(btn3);
row3.appendChild(btnSubstract);
Hbox row4 = new Hbox();
Button btnCurrency = new Button();
ZKUpdateUtil.setWidth(btnCurrency, "40px");
btnCurrency.setLabel("$");
btnCurrency.setDisabled(true);
Button btn0 = new Button();
ZKUpdateUtil.setWidth(btn0, "30px");
btn0.setLabel("0");
btn0.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', '0')");
Button btnDot = new Button();
ZKUpdateUtil.setWidth(btnDot, "30px");
btnDot.setLabel(separator);
btnDot.setDisabled(integral);
btnDot.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', '" + separator + "')");
Button btnEqual = new Button();
ZKUpdateUtil.setWidth(btnEqual, "30px");
btnEqual.setLabel("=");
btnEqual.setWidgetListener("onClick", "calc.evaluate('" + decimalBox.getId() + "','"
+ txtCalcId + "','" + separator + "')");
Button btnAdd = new Button();
ZKUpdateUtil.setWidth(btnAdd, "30px");
btnAdd.setLabel("+");
btnAdd.setWidgetListener("onClick", "calc.append('" + txtCalcId + "', ' + ')");
row4.appendChild(btnCurrency);
row4.appendChild(btnDot);
row4.appendChild(btn0);
row4.appendChild(btnEqual);
row4.appendChild(btnAdd);
vbox.appendChild(row1);
vbox.appendChild(row2);
vbox.appendChild(row3);
vbox.appendChild(row4);
popup.appendChild(vbox);
popup.setWidgetListener("onOpen", "calc.clearAll('" + txtCalcId + "')");
popup.addEventListener(Events.ON_CANCEL, e -> popup.close());
return popup;
}
/**
* @return true if it is for integer, false for decimal number
*/
public boolean isIntegral() {
return integral;
}
/**
* Set integer or decimal number mode
* @param integral true for integer mode, false for decimal number mode
*/
public void setIntegral(boolean integral) {
this.integral = integral;
if (integral)
decimalBox.setScale(0);
else
decimalBox.setScale(Decimalbox.AUTO);
}
/**
* Set enable/disable.
* Hide calculator button if set to disable.
* @param enabled
*/
public void setEnabled(boolean enabled)
{
decimalBox.setReadonly(!enabled);
decimalBox.setDisabled(!enabled);
btn.setEnabled(enabled);
if (enabled)
{
if (btn.getParent() != decimalBox.getParent())
btn.setParent(decimalBox.getParent());
}
else
{
if (btn.getParent() != null)
btn.detach();
}
if (enabled) {
LayoutUtils.removeSclass("editor-input-disd", decimalBox);
} else {
LayoutUtils.addSclass("editor-input-disd", decimalBox);
}
}
/**
* @return true if enable, false otherwise
*/
public boolean isEnabled()
{
return !decimalBox.isReadonly();
}
/**
* If evtnm is ON_CLICK, add listener to {@link #btn}.
* Otherwise, add listener to {@link #decimalBox}.
* @param evtnm
* @param listener
* @return true if listener added
*/
@Override
public boolean addEventListener(String evtnm, EventListener> listener)
{
if(Events.ON_CLICK.equals(evtnm))
{
return btn.addEventListener(evtnm, listener);
}
else
{
return decimalBox.addEventListener(evtnm, listener);
}
}
/**
* Focus to {@link #decimalBox}
*/
@Override
public void focus()
{
decimalBox.focus();
}
/**
* @return decimalBox
*/
public Decimalbox getDecimalbox()
{
return decimalBox;
}
/**
@return Button
*/
public Button getButton()
{
return btn;
}
/**
* Set to form or grid view mode.
* @param flag true for grid view mode, false otherwise
*/
public void setTableEditorMode(boolean flag) {
if (flag) {
ZKUpdateUtil.setHflex(this, "0");
LayoutUtils.addSclass("grid-editor-input", decimalBox);
LayoutUtils.addSclass("grid-editor-button", btn);
} else {
ZKUpdateUtil.setHflex(this, "1");
LayoutUtils.removeSclass("grid-editor-input", decimalBox);
LayoutUtils.removeSclass("grid-editor-button", btn);
}
}
}