/****************************************************************************** * 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 */ @Deprecated 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 = (variableName) -> {return 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 = (variableName) -> {return 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 */ @Override 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 (!Util.isEmpty(value)) 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