/****************************************************************************** * Product: Adempiere ERP & CRM Smart Business Solution * * Copyright (C) 1999-2006 ComPiere, Inc. 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 * * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA * * or via info@compiere.org or http://www.compiere.org/license.html * * Contributor(s): Victor Perez e-Evolution victor.perez@e-evolution.com * *****************************************************************************/ package org.compiere.model; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.Serializable; import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; import java.util.logging.Level; import org.adempiere.base.LookupFactoryHelper; import org.adempiere.exceptions.AdempiereException; import org.compiere.util.CLogMgt; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.DefaultEvaluatee; import org.compiere.util.DisplayType; import org.compiere.util.Env; import org.compiere.util.Evaluatee; import org.compiere.util.Evaluator; import org.compiere.util.Util; import org.idempiere.util.ParseSeq; /** * Grid Field Model. *
* Fields are a combination of AD_Field (the display attributes) * and AD_Column (the storage attributes). *
* The Field maintains the current edited value. If the value is changed,
* it fire PropertyChange "FieldValue" event.
*
* Usually editors listen to PropertyChange event of their fields.
*
* @author Jorg Janke
* @author Victor Perez , e-Evolution.SC FR [ 1757088 ], [1877902] Implement JSR 223 Scripting APIs to Callout
* https://sourceforge.net/p/adempiere/feature-requests/318/ to FR [1877902]
* @author Carlos Ruiz, qss FR [1877902]
* @author Juan David Arboleda (arboleda), GlobalQSS, [ 1795398 ] Process Parameter: add display and readonly logic
* @author Teo Sarca, teo.sarca@gmail.com
*
* Get a list of variables that this field is dependent on. * - for display logic or * - for readonly logic or * - for mandatory or * - for lookup validation ** @return ArrayList */ public ArrayList
{@code
* (a) Key/Parent/IsActive/SystemAccess
* (b) SQL Default
* (c) Column Default // system integrity
* (d) User Preference
* (e) System Preference
* (f) DataType Defaults
*
* Don't default from Context => use explicit defaultValue
* (would otherwise copy previous record)
* }
* @return default value or null
*/
public Object getDefault()
{
/**
* (a) Key/Parent/IsActive/SystemAccess
*/
if (isIgnoreDefault())
return null;
Object defaultValue = null;
if ((defaultValue = getDefault (DEFAULT_PRIORITY_ORDER)) != null){
return defaultValue;
}
/**
* No resolution
*/
if (log.isLoggable(Level.FINE)) log.fine("[NONE] " + m_vo.ColumnName);
return null;
} // getDefault
/**
* Get default of field when field not inside standard AD window (i.e not AD_Tab+AD_Field)
* @return
*/
public Object getDefaultForPanel (){
return getDefault (MSysConfig.getValue(MSysConfig.ZK_SEQ_DEFAULT_VALUE_PANEL, DEFAULT_PRIORITY_ORDER_FOR_PANEL, Env.getAD_Client_ID(m_vo.ctx)));
}
/**
* Get default value with priority define by seqGetDefaultValueStr
* @param seqGetDefaultValueStr
* @return
*/
public Object getDefault(String seqGetDefaultValueStr){
ParseSeq seqGetDefaultValue = ParseSeq.getNumberOrder(seqGetDefaultValueStr);
if (seqGetDefaultValue == null)
throw new AdempiereException ("seq define for get default value has wrong value");
return getDefault (seqGetDefaultValue);
}
/**
* Get default value with priority define by seqGetDefaultValue
* @param seqGetDefaultValue
* @return default value
*/
public Object getDefault(ParseSeq seqGetDefaultValue){
Object defaultValue = null;
for (Character seqType : seqGetDefaultValue){
if ( seqType == DEFAULT_LOGIC // default from Expression
&& m_vo.DefaultValue != null
&& m_vo.DefaultValue.toUpperCase().equals("NULL")) // IDEMPIERE-2678
return null;
defaultValue = getDefaultValueByType(seqType);
if (defaultValue != null)
return defaultValue;
}
return defaultValue;
}
/**
* * defaultValueType: * "1" special case * "2" sql default * "3" default logic * "4" user preference * "5" system preference * "6" preference for field as process parameter, info parameter,... * "7" data-type default ** @param defaultValueType * @return default value */ protected Object getDefaultValueByType (Character defaultValueType){ if (defaultValueType.equals(SPECIAL_CASE_DEFAULT)) { return defaultForSpecialCase(); }else if (defaultValueType.equals(SQL_DEFAULT)) { return defaultFromSQLExpression(); }else if (defaultValueType.equals(DEFAULT_LOGIC)) { return defaultFromExpression(); }else if (defaultValueType.equals(USER_PREFERENCE_DEFAULT) || defaultValueType.equals(SYSTEM_PREFERENCE_DEFAULT)) { return defaultFromPreference(defaultValueType); }else if (defaultValueType.equals(PANEL_PREFERENCE_DEFAULT)) { return defaultFromPreferenceForPanel(); }else if (defaultValueType.equals(DATA_TYPE_DEFAULT)) { return defaultFromDatatype(); } return null; } /** * @return true to ignore default value */ protected boolean isIgnoreDefault (){ // No defaults for these fields return (m_vo.IsKey || m_vo.displayType == DisplayType.RowID || DisplayType.isLOB(m_vo.displayType) || "Created".equals(m_vo.ColumnName) // for Created/Updated default is managed on PO, and direct inserts on DB || "Updated".equals(m_vo.ColumnName)); } /** * When field is inside standard AD window, for new record, some column is fix with special logic.
* Integer (IDs, Integer) * BigDecimal (Numbers) * Timestamp (Dates) * Boolean (YesNo) * default: String ** @param value default value * @return type dependent converted object */ private Object createDefault (String value) { // true NULL if (value == null || value.toString().length() == 0 || value.toUpperCase().equals("NULL")) return null; // see also MTable.readData try { // IDs & Integer & CreatedBy/UpdatedBy if (m_vo.ColumnName.endsWith("atedBy") || (m_vo.ColumnName.endsWith("_ID") && DisplayType.isID(m_vo.displayType))) // teo_sarca [ 1672725 ] Process parameter that ends with _ID but is boolean { try // defaults -1 => null { int ii = Integer.parseInt(value); if (ii < 0) return null; return Integer.valueOf(ii); } catch (Exception e) { log.warning("Cannot parse: " + value + " - " + e.getMessage()); } return Integer.valueOf(0); } // Integer if (m_vo.displayType == DisplayType.Integer) return Integer.valueOf(value); // Number if (DisplayType.isNumeric(m_vo.displayType)) return new BigDecimal(value); // Timestamps if (DisplayType.isDate(m_vo.displayType)) { // try timestamp format - then date format -- [ 1950305 ] java.util.Date date = null; SimpleDateFormat dateTimeFormat = DisplayType.getTimestampFormat_Default(); SimpleDateFormat dateFormat = DisplayType.getDateFormat_JDBC(); SimpleDateFormat timeFormat = DisplayType.getTimeFormat_Default(); try { if(Util.isEmpty(value, true)) { return null; } else if (m_vo.displayType == DisplayType.Date) { date = dateFormat.parse (value); } else if (m_vo.displayType == DisplayType.Time) { date = timeFormat.parse (value); } else { date = dateTimeFormat.parse (value); } } catch (java.text.ParseException e) { date = DisplayType.getDateFormat_JDBC().parse (value); } return new Timestamp (date.getTime()); } // Boolean if (m_vo.displayType == DisplayType.YesNo) return Boolean.valueOf ("Y".equals(value)); // Default return value; } catch (Exception e) { log.log(Level.SEVERE, m_vo.ColumnName + " - " + e.getMessage()); } return null; } // createDefault /** * Validate initial Field Value. Do not push direct value if it doesn't exist. * Called from GridTab.dataNew when inserting. * @return true if valid */ public boolean validateValueNoDirect() { refreshLookup(); // null if (m_value == null || m_value.toString().length() == 0) { if (isMandatory(true)) { m_error = true; return false; } else return true; } // Search not cached if (getDisplayType() == DisplayType.Search && m_lookup != null) { // need to re-set invalid values - OK BPartner in PO Line - not OK SalesRep in Invoice if (m_lookup.getDirect(m_value, false, true) == null) { if (log.isLoggable(Level.FINEST)) log.finest(m_vo.ColumnName + " Search not valid - set to null"); setValue(null, m_inserting); m_error = true; return false; } return true; } // cannot be validated if (!isLookup() || m_lookup == null) return true; if (getDisplayType() == DisplayType.ChosenMultipleSelectionList) { boolean allValid = true; for (String vals : ((String)m_value).split(",")) { if (! m_lookup.containsKeyNoDirect(vals)) { if (m_lookup.get(vals) == null) { allValid = false; break; } String name = m_lookup.get(vals).getName(); if (name.startsWith(MLookup.INACTIVE_S) && name.endsWith(MLookup.INACTIVE_E)) { allValid = false; break; } } } if (allValid) return true; } else if (DisplayType.isMultiID(getDisplayType())) { boolean allValid = true; for (String vals : ((String)m_value).split(",")) { Integer vali = Integer.valueOf(vals); if (! m_lookup.containsKeyNoDirect(vali)) { if (m_lookup.get(vali) == null) { allValid = false; break; } String name = m_lookup.get(vali).getName(); if (name.startsWith(MLookup.INACTIVE_S) && name.endsWith(MLookup.INACTIVE_E)) { allValid = false; break; } } } if (allValid) return true; } else if (m_lookup.containsKeyNoDirect(m_value)) { String name = m_lookup.get(m_value).getName(); if (! ( name.startsWith(MLookup.INACTIVE_S) && name.endsWith(MLookup.INACTIVE_E) ) ) { return true; } } // it's not null, a lookup and does not have the key if (isKey() || isParentValue()) // parents/ket are not validated return true; // special case for IDEMPIERE-2781 if ( "AD_Client_ID".equals(m_vo.ColumnName) && "0".equals(m_value.toString()) && Env.getAD_Client_ID(Env.getCtx()) == 0) return true; if (log.isLoggable(Level.FINEST)) log.finest(m_vo.ColumnName + " - set to null"); setValue(null, m_inserting); m_error = true; return false; } // validateValueNoDirect /** * Validate initial Field Value. Push direct value if it doesn't exist * Called from GridTab.setCurrentRow when inserting * @return true if valid * @deprecated use validateValueNoDirect instead */ public boolean validateValue() { // null if (m_value == null || m_value.toString().length() == 0) { if (isMandatory(true)) { m_error = true; return false; } else return true; } // Search not cached if (getDisplayType() == DisplayType.Search && m_lookup != null) { // need to re-set invalid values - OK BPartner in PO Line - not OK SalesRep in Invoice if (m_lookup.getDirect(m_value, false, true) == null) { if (log.isLoggable(Level.FINEST)) log.finest(m_vo.ColumnName + " Search not valid - set to null"); setValue(null, m_inserting); m_error = true; return false; } return true; } // cannot be validated if (!isLookup() || m_lookup == null || m_lookup.containsKey(m_value)) return true; // it's not null, a lookup and does not have the key if (isKey() || isParentValue()) // parents/ket are not validated return true; if (log.isLoggable(Level.FINEST)) log.finest(m_vo.ColumnName + " - set to null"); setValue(null, m_inserting); m_error = true; return false; } // validateValue /** * Is the Column Visible ? * @param checkContext - check environment (requires correct row position) * @return true, if visible */ public boolean isDisplayed (boolean checkContext) { return isDisplayed(m_vo.ctx, checkContext); } /** * Is the Column Visible ? * @param ctx * @param checkContext - check environment (requires correct row position) * @return true, if visible */ public boolean isDisplayed (final Properties ctx, boolean checkContext) { // ** static content ** // not displayed if (!m_vo.IsDisplayed) return false; // no restrictions if (m_vo.DisplayLogic.equals("")) return true; // ** dynamic content ** if (checkContext) { if (m_vo.DisplayLogic.startsWith(MColumn.VIRTUAL_UI_COLUMN_PREFIX)) { return Evaluator.parseSQLLogic(m_vo.DisplayLogic, m_vo.ctx, m_vo.WindowNo, m_vo.TabNo, m_vo.ColumnName); } Evaluatee evaluatee = new Evaluatee() { public String get_ValueAsString(String variableName) { return GridField.this.get_ValueAsString(ctx, variableName); } }; boolean retValue = Evaluator.evaluateLogic(evaluatee, m_vo.DisplayLogic); if (log.isLoggable(Level.FINEST)) log.finest(m_vo.ColumnName + " (" + m_vo.DisplayLogic + ") => " + retValue); return retValue; } return true; } // isDisplayed /** * Is the Grid Column Visible ? * @param checkContext - check environment (requires correct row position) * @return true, if visible */ public boolean isDisplayedGrid (boolean checkContext) { return isDisplayedGrid(m_vo.ctx, checkContext); } /** * Is the Grid Column Visible ? * @param ctx * @param checkContext - check environment (requires correct row position) * @return true, if visible */ public boolean isDisplayedGrid (final Properties ctx, boolean checkContext) { // ** static content ** // not displayed if (!m_vo.IsDisplayedGrid && !m_vo.IsDisplayed) return false; // no restrictions if (m_vo.DisplayLogic.equals("")) return true; // ** dynamic content ** if (checkContext) { if (m_vo.DisplayLogic.startsWith(MColumn.VIRTUAL_UI_COLUMN_PREFIX)) { return Evaluator.parseSQLLogic(m_vo.DisplayLogic, ctx, m_vo.WindowNo, m_vo.TabNo, m_vo.ColumnName); } Evaluatee evaluatee = new Evaluatee() { public String get_ValueAsString(String variableName) { return GridField.this.get_ValueAsString(ctx, variableName); } }; boolean retValue = Evaluator.evaluateLogic(evaluatee, m_vo.DisplayLogic); if (log.isLoggable(Level.FINEST)) log.finest(m_vo.ColumnName + " (" + m_vo.DisplayLogic + ") => " + retValue); return retValue; } return true; } // isDisplayedGrid /** * Get variable value (Evaluatee) as string * @param variableName name * @return value */ public String get_ValueAsString (String variableName) { return get_ValueAsString(m_vo.ctx, variableName); } /** * Get variable value (Evaluatee) as string * @param ctx * @param variableName name * @return value */ public String get_ValueAsString (Properties ctx, String variableName) { if (m_parentEvaluatee != null) { String value = m_parentEvaluatee.get_ValueAsString(variableName); if (value != null) return value; } return new DefaultEvaluatee(getGridTab(), m_vo.WindowNo, m_vo.TabNo).get_ValueAsString(ctx, variableName); } // get_ValueAsString /** * Add display dependencies to given List. * Source: DisplayLogic. * @param list list to be added to */ public void addDependencies (ArrayList
* Do not update context - called from GridTab.setCurrentRow. * Send Bean PropertyChange event if there is a change (i.e current value is not null). */ public void setValue () { if (m_valueNoFire) // set the old value m_oldValue = m_value; m_value = null; m_inserting = false; m_error = false; // reset error // Does not fire, if same value m_propertyChangeListeners.firePropertyChange(PROPERTY, m_oldValue, m_value); } // setValue /** * Set Value to null. *
* Do update context - called from GridTab.setCurrentRow. * Send Bean PropertyChange event if there is a change (i.e current value is not null). */ public void setValueAndUpdateContext () { if (m_valueNoFire) // set the old value m_oldValue = m_value; m_value = null; m_inserting = false; m_error = false; // reset error // [ 1881480 ] Navigation problem between tabs Env.setContext(m_vo.ctx, m_vo.WindowNo, m_vo.ColumnName, (String) m_value); // Does not fire, if same value m_propertyChangeListeners.firePropertyChange(PROPERTY, m_oldValue, m_value); } // setValue /** * Set Value. *
* Update context, if not text or RowID;
* Send Bean PropertyChange event if there is a change.
* @param newValue new value
* @param inserting true if inserting
*/
public void setValue (Object newValue, boolean inserting)
{
if (m_valueNoFire) // set the old value
m_oldValue = m_value;
m_value = newValue;
m_inserting = inserting;
m_error = false; // reset error
updateContext();
// Does not fire, if same value
Object oldValue = m_oldValue;
if (inserting)
oldValue = INSERTING;
m_propertyChangeListeners.firePropertyChange(PROPERTY, oldValue, m_value);
} // setValue
/**
* Update env. context with current value
*/
public void updateContext() {
// Set Context
if (m_vo.displayType == DisplayType.Text
|| m_vo.displayType == DisplayType.Memo
|| m_vo.displayType == DisplayType.TextLong
|| m_vo.displayType == DisplayType.JSON
|| m_vo.displayType == DisplayType.Binary
|| m_vo.displayType == DisplayType.RowID
|| isEncrypted())
; // ignore
else if (m_value instanceof Boolean)
{
backupValue(); // teo_sarca [ 1699826 ]
if (!isParentTabField() && isUpdateWindowContext())
{
Env.setContext(m_vo.ctx, m_vo.WindowNo, m_vo.ColumnName,
((Boolean)m_value).booleanValue());
}
if (m_gridTab != null) {
Env.setContext(m_vo.ctx, m_vo.WindowNo, m_vo.TabNo, m_vo.ColumnName,
m_value==null ? null : (((Boolean)m_value) ? "Y" : "N"));
}
}
else if (m_value instanceof Timestamp)
{
backupValue(); // teo_sarca [ 1699826 ]
if (!isParentTabField() && isUpdateWindowContext())
{
Env.setContext(m_vo.ctx, m_vo.WindowNo, m_vo.ColumnName, (Timestamp)m_value);
}
// BUG:3075946 KTU - Fix Thai Date
String stringValue = null;
if (m_value != null && !m_value.toString().equals("")) {
Calendar c1 = Calendar.getInstance();
c1.setTime((Date) m_value);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
stringValue = sdf.format(c1.getTime());
}
if (m_gridTab != null) {
Env.setContext(m_vo.ctx, m_vo.WindowNo, m_vo.TabNo, m_vo.ColumnName, stringValue);
}
// KTU - Fix Thai Date
}
else
{
backupValue(); // teo_sarca [ 1699826 ]
if (!isParentTabField() && isUpdateWindowContext())
{
Env.setContext(m_vo.ctx, m_vo.WindowNo, m_vo.ColumnName,
m_value==null ? null : m_value.toString());
}
if (m_gridTab != null) {
Env.setContext(m_vo.ctx, m_vo.WindowNo, m_vo.TabNo, m_vo.ColumnName,
m_value==null ? null : m_value.toString());
}
}
}
/**
* @return AD_LabelStyle_ID
*/
public int getAD_LabelStyle_ID()
{
return m_vo.AD_LabelStyle_ID;
}
/**
* @return AD_FieldStyle_ID
*/
public int getAD_FieldStyle_ID()
{
return m_vo.AD_FieldStyle_ID;
}
/**
* Set Value and Validate
* @param newValue value
* @param inserting insert
* @return null or error message
*/
public String setValueValidate (String newValue, boolean inserting)
{
if (newValue == null)
setValue();
// Data Type Test
int dt = getDisplayType();
try
{
// Return Integer
if (dt == DisplayType.Integer
|| (DisplayType.isID(dt) && getColumnName().endsWith("_ID")))
{
int i = Integer.parseInt(newValue);
setValue (Integer.valueOf(i), inserting);
}
// Return BigDecimal
else if (DisplayType.isNumeric(dt))
{
BigDecimal value = (BigDecimal)DisplayType.getNumberFormat(dt).parse(newValue);
setValue (value, inserting);
return null;
}
// Return Timestamp
else if (DisplayType.isDate(dt))
{
long time = DisplayType.getDateFormat_JDBC().parse(newValue).getTime();
setValue (new Timestamp(time), inserting);
return null;
}
// Return Boolean
else if (dt == DisplayType.YesNo)
{
Boolean value = null;
if (newValue.equals("Y"))
value = Boolean.TRUE;
else if (newValue.equals("N"))
value = Boolean.FALSE;
else
return getColumnName() + " = " + newValue + " - Must be Y/N";
setValue (value, inserting);
return null;
}
else if (DisplayType.isText(dt))
{
setValue (newValue, inserting);
return null;
}
else
return getColumnName() + " not mapped "
+ DisplayType.getDescription(dt);
}
catch (Exception ex)
{
log.log(Level.SEVERE, "Value=" + newValue, ex);
String error = ex.getLocalizedMessage();
if (error == null || error.length() == 0)
error = ex.toString();
return getColumnName() + " = " + newValue + " - " + error;
}
// ID - test ID
if (!DisplayType.isID(dt))
return null;
return null;
} // setValueValidate
/**
* Get Value
* @return current value
*/
public Object getValue()
{
return m_value;
} // getValue
/**
* @param value if false property change will always be fires
*/
public void setValueNoFire (boolean value)
{
m_valueNoFire = value;
} // setOldValue
/**
* Get old/previous Value.
* Called from MTab.processCallout.
* @return old value
*/
public Object getOldValue()
{
return m_oldValue;
} // getOldValue
/**
* Set Error Value (the value, which caused some Error)
* @param errorValue error value
*/
public void setErrorValue (String errorValue)
{
m_errorValue = errorValue;
m_errorValueFlag = true;
} // setErrorValue
/**
* Get Error Value (the value, which caused some Error) AND reset error value to null
* @return error value
*/
public String getErrorValue ()
{
String s = m_errorValue;
m_errorValue = null;
m_errorValueFlag = false;
return s;
} // getErrorValue
/**
* Get error value flag AND reset error value flag to false
* @return true if error value is set
*/
public boolean isErrorValue()
{
boolean b = m_errorValueFlag;
m_errorValueFlag = false;
return b;
} // isErrorValue
/**
* Overwrite default DisplayLength
* @param length new length
*/
public void setDisplayLength (int length)
{
m_vo.DisplayLength = length;
} // setDisplayLength
/**
* Overwrite Displayed
* @param displayed true if displayed
*/
public void setDisplayed (boolean displayed)
{
m_vo.IsDisplayed = displayed;
} // setDisplayed
/**
* Create Mnemonic for field
* @return no for r/o, client, org, document no
*/
public boolean isCreateMnemonic()
{
if (isReadOnly()
|| m_vo.ColumnName.equals("AD_Client_ID")
|| m_vo.ColumnName.equals("AD_Org_ID")
|| m_vo.ColumnName.equals("DocumentNo"))
return false;
return true;
}
/**
* Get Label Mnemonic
* @return Mnemonic
*/
public char getMnemonic()
{
return m_mnemonic;
} // getMnemonic
/**
* Set Label Mnemonic
* @param mnemonic Mnemonic
*/
public void setMnemonic (char mnemonic)
{
m_mnemonic = mnemonic;
} // setMnemonic
/**
* String representation
* @return string representation
*/
public String toString()
{
StringBuilder sb = new StringBuilder("GridField[")
.append(m_vo.ColumnName).append("=").append(m_value);
if (isKey())
sb.append("(Key)");
if (isParentColumn())
sb.append("(Parent)");
sb.append(", IsDisplayed="+m_vo.IsDisplayed);
sb.append("]");
return sb.toString();
} // toString
/**
* Extended String representation
* @return string representation
*/
public String toStringX()
{
StringBuilder sb = new StringBuilder("GridField[");
sb.append(m_vo.ColumnName).append("=").append(m_value)
.append(",DisplayType=").append(getDisplayType())
.append("]");
return sb.toString();
} // toStringX
/**
* Remove Property Change Listener
* @param l listener
*/
public synchronized void removePropertyChangeListener(PropertyChangeListener l)
{
m_propertyChangeListeners.removePropertyChangeListener(l);
}
/**
* Add Property Change Listener
* @param l listener
*/
public synchronized void addPropertyChangeListener(PropertyChangeListener l)
{
m_propertyChangeListeners.addPropertyChangeListener(l);
}
/**
* Create GridFields for AD_Tab
* @param ctx context
* @param WindowNo window
* @param TabNo tab no
* @param AD_Tab_ID tab
* @return array of all fields in display order
*/
public static GridField[] createFields (Properties ctx, int WindowNo, int TabNo,
int AD_Tab_ID)
{
ArrayList