/* * Copyright (c) 2003,2009 Declarative Engineering LLC. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Declarative Engineering LLC * verson 1 which accompanies this distribution, and is available at * http://declarativeengineering.com/legal/DE_Developer_License_v1.txt */ package com.foundation.tcv.swt.client; import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.Locale; import org.eclipse.swt.SWT; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.Text; import com.common.comparison.Comparator; import com.common.comparison.IComparator; import com.common.debug.Debug; import com.common.thread.IRunnable; import com.foundation.tcv.client.view.ResourceHolder; import com.foundation.tcv.swt.*; import com.foundation.tcv.view.*; import com.foundation.view.ControlDecoration; import com.foundation.view.ISingleResourceAssociationChangeListener; import com.foundation.view.JefColor; public class TextField extends ScrollableComponent implements VerifyListener, ModifyListener, ITextField, FocusListener, ShellListener { /** Used to work around the SWT code in order to selectAll when traversing to the control. */ public static boolean disableFocusSelection = false; /** The format used by the text field for displaying and interperating data. */ private Format format = null; /** Whether the text should automatically synchronize when altered in the view. */ private boolean autoSynchronizeValue = false; /** The value last set by the server component. */ private Object serverValue = null; /** The delay to be used when auto synchronizing changes. */ private long autoSynchronizeValueDelay = 500; /** The task that auto synchronizes the text after a short delay. */ private Task autoSynchronizeValueTask = null; /** Whether the text is currently in the process of refreshing. This is used to suppress some functionality for a short time. */ private boolean isRefreshingText = false; /** Whether the field is read only. */ private boolean isReadOnly = false; /** Whether the contents are to be selected when the control takes focus. */ private boolean selectOnFocus = false; /** Whether we are currently showing the shadow text. If this is true then we must reset the text forground color and the displayed value when we gain focus. */ private boolean isShowingShadowText = true; /** A holder for the value of the shadow text. */ private ResourceHolder shadowTextHolder = new ResourceHolder(this); /** A holder for the value of the shadow text color. */ private ResourceHolder shadowTextColorHolder = new ResourceHolder(this); /** The color used by the shadow text. */ private Color shadowTextForegroundSwtColor = null; /** The foreground color to override the default component foregound color, or null to not override. */ private Color currentFormatForegroundColor = null; /** The foreground color to override the default component foregound color, or null to not override. */ private JefColor currentFormatForegroundJefColor = null; public abstract class Format implements IFormat { /** The last known model value. This is used to determine whether the user has altered the text in the field. */ private Object serverModelValue = null; /** The current decoration displaying the notification that the original value has changed. */ protected ControlDecoration originalValueChangedDecoration = null; /** * Initializes this component only. */ protected void internalViewInitialize() { }//internalViewInitialize()// /** * Releases this component only. */ protected void internalViewRelease() { }//internalViewRelease()// /** * Synchronizes this component only. */ protected void internalViewSynchronize() { }//internalViewSynchronize()// /** * Gets the view's value converted to the model data type. * @return The current value displayed by the view. */ protected abstract Object getModelValue(); /** * Sets the model value in the model. * @param value The new model value to be stored in the model. */ protected abstract void setModelValue(Object value); /** * Verifies that the text is acceptable. * @param event The event. */ protected abstract void verifyText(VerifyEvent event); /** * Called when the control loses focus. * @param event The focus event. */ protected abstract void focusLost(FocusEvent event); /** * Refreshes the view's display of the value. * @param value The model value. * @param flags The flags associated with the value change. */ public abstract void refreshValue(Object value, int flags); /** * Processes the view message whose destination is the format. * @param viewMessage The view message to be processed. Note that the view message's secondary integer cannot be used by the format. * @return The result of the view message. */ public Object internalProcessMessage(ViewMessage viewMessage) { Debug.log(new RuntimeException("Error: Unexpected view message found.")); return null; }//internalProcessMessage()// /** * Called when a resource holder has been altered. * @param resourceHolder The resource holder that whose value has changed. * @param oldValue The old value from the holder. * @param newValue The new value in the holder. * @return Whether the format processed the change. */ public boolean internalResourceHolderChanged(ResourceHolder resourceHolder, Object oldValue, Object newValue, int flags) { return false; }//internalResourceHolderChanged()// /** * Sends a message to the server component. * @param messageNumber The message number which will be used by the client component to handle the message and interperate the parameters. * @param messageData The data associated with the message. * @param secondaryMessageData The optional secondary message data reference. * @param messageInteger The numeric data associated with the message. */ public void sendMessage(int messageNumber, Object messageData, Object secondaryMessageData, int messageInteger) { TextField.this.sendMessage(MESSAGE_FORMAT_MESSAGE, messageData, secondaryMessageData, messageInteger, messageNumber); }//sendMessage()// /** * Gets the last known model value for the cell. * @return The last known value in the model. */ public Object getServerModelValue() { return serverModelValue; }//getServerModelValue()// /** * Sets the last known model value for the cell. * @param modelValue The last known value in the model. */ public void setServerModelValue(Object modelValue) { this.serverModelValue = modelValue; }//setServerModelValue()// /** * Provides the format an opportunity to act upon the state of the shadow text flag being altered. */ public void updateShadowTextState() { //Does nothing.// }//updateShadowTextState()// }//Format// public class TextFormat extends Format implements ITextFormat { /** A holder for the value of the font. */ private ResourceHolder valueHolder = new ResourceHolder(TextField.this); /** Whether a null value should be used when there is no text. */ private boolean useNull = true; /** The echo character to be used, or null if no echo character should be set. */ private Character echoCharacter = null; public TextFormat() { }//TextFormat()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#internalViewInitialize() */ protected void internalViewInitialize() { }//internalViewInitialize()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#internalViewRelease() */ protected void internalViewRelease() { valueHolder.release(); }//internalViewRelease()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#internalViewSynchronize() */ protected void internalViewSynchronize() { }//internalViewSynchronize()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#internalProcessMessage(com.foundation.tcv.view.ViewMessage) */ public Object internalProcessMessage(ViewMessage viewMessage) { Object result = null; switch(viewMessage.getMessageNumber()) { case MESSAGE_SET_VALUE: { valueHolder.setValue(viewMessage.getMessageData(), viewMessage.getMessageInteger()); break; }//case// case MESSAGE_SET_ECHO_CHARACTER: { this.echoCharacter = (Character) viewMessage.getMessageData(); if(!isShowingShadowText) { getSwtText().setEchoChar(echoCharacter.charValue()); }//if// // getSwtText().setEchoChar(((Character) viewMessage.getMessageData()).charValue()); // getSwtText().pack(true); // getSwtText().getShell().layout(true, true); break; }//case// case MESSAGE_SET_ORIGINAL_VALUE: { Object originalValue = viewMessage.getMessageData(); boolean isCleared = ((Boolean) viewMessage.getMessageSecondaryData()).booleanValue(); internalOnModelExternallyChanged(isCleared, originalValue); break; }//case// case MESSAGE_SET_USE_NULL: { useNull = ((Boolean) viewMessage.getMessageData()).booleanValue(); break; }//case// default: { result = super.internalProcessMessage(viewMessage); }//default// }//switch// return result; }//internalProcessMessage()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#getModelValue() */ protected Object getModelValue() { String text = getSwtText().getText(); Object value = null; if((!isShowingShadowText) && (text != null) && (text.length() > 0)) { value = text; }//if// else if(!useNull) { value = ""; }//else// return value; }//getModelValue()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#refreshValue(java.lang.Object, int) */ public void refreshValue(Object value, int flags) { String serverModelValue = getServerModelValue() == null || ((String) getServerModelValue()).length() == 0 ? null : (String) getServerModelValue(); String newValue = value == null ? null : value.toString(); String text = getSwtText().getText(); //Ensure we have an actual string or null.// newValue = newValue == null || newValue.length() == 0 ? null : newValue; //Ensure we have an actual string or null.// text = isShowingShadowText || text == null || text.length() == 0 ? null : text; //Check to see if the user has altered the value. If the user has then don't update the control with the new value, instead notify the user of the underlying model change.// if(flags != ISingleResourceAssociationChangeListener.FLAG_CONTAINING_MODEL_CHANGED && !Comparator.equals(serverModelValue, text) && !Comparator.equals(newValue, text)) { setServerModelValue(newValue); //Notify the user of the underlying model change.// internalOnModelExternallyChanged(false, value); }//if// else { //Remove any old notification.// removeDecoration(originalValueChangedDecoration); originalValueChangedDecoration = null; //Save the current server model value.// setServerModelValue(newValue); //Update the display.// internalSetText((String) getServerModelValue()); }//else// }//refreshValue()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#internalResourceHolderChanged(com.foundation.tcv.client.view.ResourceHolder, java.lang.Object, java.lang.Object) */ public boolean internalResourceHolderChanged(ResourceHolder resourceHolder, Object oldValue, Object newValue, int flags) { boolean result = false; if(resourceHolder == valueHolder) { refreshValue(newValue, flags); result = true; }//if// else { result = super.internalResourceHolderChanged(resourceHolder, oldValue, newValue, flags); }//else// return result; }//internalResourceHolderChanged()// /** * Sets the original value which should generate a change notification. * @param isCleared Whether the value has been cleared, and the passed value should be ignored. * @param value The value set in the model as the new original value. */ protected void internalOnModelExternallyChanged(boolean isCleared, Object value) { removeDecoration(originalValueChangedDecoration); originalValueChangedDecoration = null; if(!isCleared && !isReadOnly) { //TODO: Use the original value. addDecoration(originalValueChangedDecoration = getChangeControlDecoration()); }//if// }//internalOnModelExternallyChanged()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#setModelValue(java.lang.Object) */ protected void setModelValue(Object value) { sendMessage(MESSAGE_SYNCHRONIZE_VALUE, value, null, -1); //Invalidate the holder's value so that future updates from the server will not be matched against the previous update from the server and will always generate a change event.// valueHolder.invalidateValue(); }//setModelValue()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#verifyText(org.eclipse.swt.events.VerifyEvent) */ protected void verifyText(VerifyEvent event) { }//verifyText()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#focusLost(org.eclipse.swt.events.FocusEvent) */ protected void focusLost(FocusEvent event) { }//focusLost()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#updateShadowTextState() */ public void updateShadowTextState() { if(echoCharacter != null) { if(isShowingShadowText) { getSwtText().setEchoChar('\0'); }//if// else { getSwtText().setEchoChar(echoCharacter.charValue()); }//else// }//if// }//updateShadowTextState()// }//TextFormat// public class IntegerFormat extends Format implements IIntegerFormat { /** A holder for the value of the negative number color. */ private ResourceHolder negativeColorHolder = new ResourceHolder(TextField.this); /** The foreground color to use with negative numbers, or null if not used. */ private JefColor negativeForegroundColor = null; /** The locale used. */ private Locale locale = null; /** The maximum value to allow. */ private BigDecimal maxValue = null; /** The minimum value to allow. */ private BigDecimal minValue = null; /** The maximum number of integer digits to display. */ private Integer maxIntegerDigits = null; /** The minimum number of integer digits to display. */ private Integer minIntegerDigits = null; /** Whether to display grouping characters making it easier to read large numbers. */ private Boolean group = null; /** The data type used by the model. This is necessary because currently there isn't a way to detect this if the model's getter method results in an abstract class such as Number or Object. */ private int modelType = DATA_TYPE_INTEGER; /** The minimum number value allowed. This is the larger of minValue or the minimum value allowed by the modelType. */ private BigDecimal minNumber = null; /** The maximum number value allowed. This is the smaller of maxValue or the maximum value allowed by the modelType. */ private BigDecimal maxNumber = null; /** The number format used to convert the number to/from text. */ private DecimalFormat numberFormat = null; /** The custom pattern used to format the number. */ private String formatPattern = null; /** Whether the decimal character is allowed. */ private boolean allowDecimal = false; /** Whether a negative sign is allowed. */ private boolean allowNegative = true; /** Metadata from the number format instance. */ protected char decimal = '\0'; /** Metadata from the number format instance. */ protected char monitaryDecimal = '\0'; /** Metadata from the number format instance. */ protected boolean isMonitary = false; /** Metadata from the number format instance. */ protected char zero = '\0'; /** Metadata from the number format instance. */ protected char perMill = '\0'; /** Metadata from the number format instance. */ protected char groupSeparator = '\0'; /** Metadata from the number format instance. */ protected String positivePrefix = null; /** Metadata from the number format instance. */ protected String positiveSuffix = null; /** Metadata from the number format instance. */ protected String negativePrefix = null; /** Metadata from the number format instance. */ protected String negativeSuffix = null; /** Metadata from the number format instance. */ protected char minusSign = '\0'; /** Metadata from the number format instance. */ protected String exponentSeparator = null; /** Used to flag that the user is actively modifying the number and it does not conform to requirements (it is below the min, above the max, too many digits before/after the decimal, etc. */ protected boolean isModifying = false; /** Whether the formatting of the text is updated in realtime. */ protected boolean realtime = true; /** The default value - used if the field is empty or not parseable. */ protected String defaultValue = null; public IntegerFormat() { }//IntegerFormat()// /** * Gets the currently used locale. * @return The locale used to format the field. */ protected Locale getLocale() { return locale; }//getLocale()// /** * Gets the maximum value allowed. * @return The maximum value. */ protected BigDecimal getMaxValue() { return maxValue; }//getMaxValue()// /** * Gets the minimum value allowed. * @return The minimum value. */ protected BigDecimal getMinValue() { return minValue; }//getMinValue()// /** * Gets the maximum number of integer digits. * @return The maximum number of integer digits, or null to use the default value. */ protected Integer getMaxIntegerDigits() { return maxIntegerDigits; }//getMaxIntegerDigits()// /** * Gets the minimum number of integer digits. * @return The minimum number of integer digits, or null to use the default value. */ protected Integer getMinIntegerDigits() { return minIntegerDigits; }//getMinIntegerDigits()// /** * Gets whether the text should update its formatting in realtime. * @return Whether the text is formatted as the user types, versus when the control loses focus. */ public boolean getRealtime() { return realtime; }//getRealtime()// /** * Sets whether the text should update its formatting in realtime. * @param realtime Whether the text is formatted as the user types, versus when the control loses focus. */ public void setRealtime(boolean realtime) { this.realtime = realtime; }//setRealtime()// /** * Gets the grouping flag in the number formatter. * @return Whether the numbers should be grouped to enhance viewing of long numbers. */ protected Boolean getGroup() { return group; }//getGroup()// /** * Gets the data type code used by the model for storing the number. * @return The data type the model expects when the data is saved. */ protected int getModelType() { return modelType; }//getModelType()// /** * Creates the new number format to be used. * @return The uninitialized number format used to convert numbers to/from text. */ public DecimalFormat createNumberFormat() { Locale locale = getLocale(); String pattern = getFormatPattern(); DecimalFormat result = (DecimalFormat) (locale != null ? DecimalFormat.getIntegerInstance(locale) : DecimalFormat.getIntegerInstance()); if(pattern != null) { result.applyPattern(pattern); }//if// return result; }//createNumberFormat()// /** * Initializes the number format. * @param numberFormat The number format used to convert numbers to/from text. */ public void initializeNumberFormat(DecimalFormat numberFormat) { if(getGroup() != null) { numberFormat.setGroupingUsed(getGroup().booleanValue()); }//if// if(getMaxIntegerDigits() != null) { numberFormat.setMaximumIntegerDigits(getMaxIntegerDigits().intValue()); }//if// if(getMinIntegerDigits() != null) { numberFormat.setMinimumIntegerDigits(getMinIntegerDigits().intValue()); }//if// numberFormat.setParseBigDecimal(true); }//initializeNumberFormat()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#internalViewInitialize() */ protected void internalViewInitialize() { initializeNumberFormat(); }//internalViewInitialize()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#internalViewRelease() */ protected void internalViewRelease() { negativeColorHolder.release(); }//internalViewRelease()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#internalViewSynchronize() */ protected void internalViewSynchronize() { }//internalViewSynchronize()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#internalProcessMessage(com.foundation.tcv.view.ViewMessage) */ public Object internalProcessMessage(ViewMessage viewMessage) { Object result = null; switch(viewMessage.getMessageNumber()) { case MESSAGE_SET_VALUE: { refreshValue((Number) viewMessage.getMessageData(), viewMessage.getMessageInteger()); break; }//case// case MESSAGE_SET_MAX_INTEGER_DIGITS: { maxIntegerDigits = (Integer) viewMessage.getMessageData(); break; }//case// case MESSAGE_SET_MIN_INTEGER_DIGITS: { minIntegerDigits = (Integer) viewMessage.getMessageData(); break; }//case// case MESSAGE_SET_REALTIME: { setRealtime(viewMessage.getMessageInteger() == 1); break; }//case// case MESSAGE_SET_GROUP: { group = (Boolean) viewMessage.getMessageData(); break; }//case// case MESSAGE_SET_MODEL_TYPE: { setModelType(viewMessage.getMessageInteger()); break; }//case// case MESSAGE_SET_MAX_VALUE: { maxValue = (BigDecimal) viewMessage.getMessageData(); refreshMaxNumber(); processText(); break; }//case// case MESSAGE_SET_MIN_VALUE: { minValue = (BigDecimal) viewMessage.getMessageData(); refreshMinNumber(); processText(); break; }//case// case MESSAGE_SET_FORMAT: { formatPattern = (String) viewMessage.getMessageData(); initializeNumberFormat(); break; }//case// case MESSAGE_SET_LOCALE: { String[] localeData = (String[]) viewMessage.getMessageData(); locale = new Locale(localeData[0], localeData[1], localeData[2]); if(isInitialized()) { initializeNumberFormat(); }//if// break; }//case// case MESSAGE_SET_NEGATIVE_COLOR: { negativeColorHolder.setValue(viewMessage.getMessageData()); break; }//case// case MESSAGE_SET_ORIGINAL_VALUE: { Object originalValue = viewMessage.getMessageData(); boolean isCleared = ((Boolean) viewMessage.getMessageSecondaryData()).booleanValue(); internalOnModelExternallyChanged(isCleared, originalValue); break; }//case// case MESSAGE_SET_DEFAULT_VALUE: { this.defaultValue = (String) viewMessage.getMessageData(); break; }//case// default: { result = super.internalProcessMessage(viewMessage); }//default// }//switch// return result; }//internalProcessMessage()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#internalResourceHolderChanged(com.foundation.tcv.client.view.ResourceHolder, java.lang.Object, java.lang.Object) */ public boolean internalResourceHolderChanged(ResourceHolder resourceHolder, Object oldValue, Object newValue, int flags) { boolean result = true; if(resourceHolder == negativeColorHolder) { setNegativeForegroundColor((JefColor) newValue); processText(); }//if// else { result = super.internalResourceHolderChanged(resourceHolder, oldValue, newValue, flags); }//else// return result; }//internalResourceHolderChanged()// /** * Sets the data type code used by the model for storing the number. * @param modelType The data type the model expects when the data is saved. */ public void setModelType(int modelType) { if(!isInitialized()) { if(modelType == DATA_TYPE_BIG_DECIMAL || modelType == DATA_TYPE_DOUBLE || modelType == DATA_TYPE_FLOAT) { setAllowDecimal(true); }//if// this.modelType = modelType; refreshMaxNumber(); refreshMinNumber(); }//if// }//setModelType()// /** * Gets whether the decimal character is allowed. * @return Whether the number supports decimal notation. */ public boolean getAllowDecimal() { return allowDecimal; }//getAllowDecimal()// /** * Sets whether the decimal character is allowed. * @param allowDecimal Whether the number supports decimal notation. */ public void setAllowDecimal(boolean allowDecimal) { this.allowDecimal = allowDecimal; }//setAllowDecimal()// /** * Gets whether the negative character is allowed. * @return Whether the number supports negative notation. */ public boolean getAllowNegative() { return allowNegative; }//getAllowNegative()// /** * Sets whether the negative character is allowed. * @param allowNegative Whether the number supports negative notation. */ public void setAllowNegative(boolean allowNegative) { this.allowNegative = allowNegative; }//setAllowNegative()// /** * Gets the foreground color to be used if the value is negative. * @return The color used for the text when the value is negative. */ public JefColor getNegativeForegroundColor() { return negativeForegroundColor; }//getNegativeForegroundColor()// /** * Sets the foreground color to be used if the value is negative. * @param negativeForegroundColor The color used for the text when the value is negative. */ public void setNegativeForegroundColor(JefColor negativeForegroundColor) { this.negativeForegroundColor = negativeForegroundColor; processText(); }//setNegativeForegroundColor()// /** * Gets the minimum number allowed. * @return The smallest valid number. */ public BigDecimal getMinNumber() { return minNumber; }//getMinNumber()// /** * Gets the maximum number allowed. * @return The largest valid number. */ public BigDecimal getMaxNumber() { return maxNumber; }//getMaxNumber()// /** * Gets the number format being used to convert to/from text. * @return The format used to convert numbers to/from text. */ public DecimalFormat getNumberFormat() { return numberFormat; }//getNumberFormat()// /** * Gets the custom format pattern currently active. * @return The custom format pattern which must adhere to the java.text.DecimalFormat class's rules. */ protected String getFormatPattern() { return formatPattern; }//getFormatPattern()// /** * Gets the model type's maximum value. * @return The maximum value for the model type. */ private BigDecimal getMaxModelTypeValue() { BigDecimal result = null; switch(getModelType()) { case DATA_TYPE_BYTE: { result = new BigDecimal(Byte.MAX_VALUE); break; }//case// case DATA_TYPE_SHORT: { result = new BigDecimal(Short.MAX_VALUE); break; }//case// case DATA_TYPE_INTEGER: { result = new BigDecimal(Integer.MAX_VALUE); break; }//case// case DATA_TYPE_LONG: { result = new BigDecimal(Long.MAX_VALUE); break; }//case// case DATA_TYPE_FLOAT: { result = new BigDecimal(Float.MAX_VALUE); break; }//case// case DATA_TYPE_DOUBLE: { result = new BigDecimal(Double.MAX_VALUE); break; }//case// case DATA_TYPE_BIG_DECIMAL: { result = null; break; }//case// }//switch// return result; }//getMaxModelTypeValue()// /** * Gets the model type's minimum value. * @return The minimum value for the model type. */ private BigDecimal getMinModelTypeValue() { BigDecimal result = null; switch(getModelType()) { case DATA_TYPE_BYTE: { result = new BigDecimal(Byte.MIN_VALUE); break; }//case// case DATA_TYPE_SHORT: { result = new BigDecimal(Short.MIN_VALUE); break; }//case// case DATA_TYPE_INTEGER: { result = new BigDecimal(Integer.MIN_VALUE); break; }//case// case DATA_TYPE_LONG: { result = new BigDecimal(Long.MIN_VALUE); break; }//case// case DATA_TYPE_FLOAT: { result = new BigDecimal(Float.MAX_VALUE).negate(); break; }//case// case DATA_TYPE_DOUBLE: { result = new BigDecimal(Double.MAX_VALUE).negate(); break; }//case// case DATA_TYPE_BIG_DECIMAL: { result = null; break; }//case// }//switch// return result; }//getMinModelTypeValue()// /** * Refreshes the maximum number allowed based on the maximum value and the model data type. */ protected void refreshMaxNumber() { BigDecimal modelTypeMax = getMaxModelTypeValue(); BigDecimal userMax = maxValue; maxNumber = modelTypeMax == null ? userMax == null ? null : userMax : userMax == null ? modelTypeMax : modelTypeMax.compareTo(userMax) == IComparator.GREATER_THAN ? userMax : modelTypeMax; }//refreshMaxNumber()// /** * Refreshes the minimum number allowed based on the minimum value and the model data type. */ protected void refreshMinNumber() { BigDecimal modelTypeMin = getMinModelTypeValue(); BigDecimal userMin = minValue; minNumber = modelTypeMin == null ? userMin == null ? null : userMin : userMin == null ? modelTypeMin : modelTypeMin.compareTo(userMin) == IComparator.LESS_THAN ? userMin : modelTypeMin; allowNegative = minNumber == null ? true : minNumber.compareTo(new BigDecimal(0)) < 0; }//refreshMinNumber()// /** * Initializes the number format. */ protected void initializeNumberFormat() { numberFormat = createNumberFormat(); initializeNumberFormat(numberFormat); minusSign = getNumberFormat().getDecimalFormatSymbols().getMinusSign(); exponentSeparator = "E"; //TODO: Re-enable this for jdk 1.6 - previous jdk's don't support this call.// //exponentSeparator = getNumberFormat().getDecimalFormatSymbols().getExponentSeparator(); groupSeparator = getNumberFormat().getDecimalFormatSymbols().getGroupingSeparator(); //Note: This checks to see if the pattern is considered monitary by the formatter - the pattern may contain monitary symbols, but if it doesn't contain the international symbol then the pattern is not considered monitary.// if(getNumberFormat().toPattern().indexOf("�") != -1 || getNumberFormat().toPattern().indexOf(getNumberFormat().getDecimalFormatSymbols().getCurrencySymbol()) != -1) { decimal = getNumberFormat().getDecimalFormatSymbols().getMonetaryDecimalSeparator(); }//if// else { decimal = getNumberFormat().getDecimalFormatSymbols().getDecimalSeparator(); }//else// zero = getNumberFormat().getDecimalFormatSymbols().getZeroDigit(); perMill = getNumberFormat().getDecimalFormatSymbols().getPerMill(); positivePrefix = getNumberFormat().getPositivePrefix(); positiveSuffix = getNumberFormat().getPositiveSuffix(); negativePrefix = getNumberFormat().getNegativePrefix(); negativeSuffix = getNumberFormat().getNegativeSuffix(); internalRefreshValue((Number) getModelValue()); }//initializeNumberFormat()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#refreshValue(java.lang.Object, int) */ public void refreshValue(Object value, int flags) { //Number displayedValue = (Number) getModelValue(); DecimalFormat numberFormat = getNumberFormat(); String text = getSwtText().getText(); Object serverModelValue = getServerModelValue(); boolean doesServerValueMatchDisplayedValue; boolean doesNewValueMatchDisplayedValue; if(value != null) { //Convert the value based on the expected data type.// switch(modelType) { case DATA_TYPE_BYTE: { if(!(value instanceof Byte)) { if(value instanceof Number) { value = new Byte(((Number) value).byteValue()); }//if// else { value = new Byte((byte) 0); }//else// }//if// break; }//case// case DATA_TYPE_SHORT: { if(!(value instanceof Short)) { if(value instanceof Number) { value = new Short(((Number) value).shortValue()); }//if// else { value = new Short((short) 0); }//else// }//if// break; }//case// case DATA_TYPE_INTEGER: { if(!(value instanceof Integer)) { if(value instanceof Number) { value = new Integer(((Number) value).intValue()); }//if// else { value = new Integer(0); }//else// }//if// break; }//case// case DATA_TYPE_LONG: { if(!(value instanceof Long)) { if(value instanceof Number) { value = new Long(((Number) value).longValue()); }//if// else { value = new Long(0); }//else// }//if// break; }//case// case DATA_TYPE_FLOAT: { if(!(value instanceof Float)) { if(value instanceof Number) { value = new Float(((Number) value).floatValue()); }//if// else { value = new Float(0); }//else// }//if// break; }//case// case DATA_TYPE_DOUBLE: { if(!(value instanceof Double)) { if(value instanceof Number) { value = new Double(((Number) value).doubleValue()); }//if// else { value = new Double(0); }//else// }//if// break; }//case// case DATA_TYPE_BIG_DECIMAL: { if(!(value instanceof BigDecimal)) { if(value instanceof Number) { value = new BigDecimal(((Number) value).doubleValue()); }//if// else { value = new BigDecimal(0); }//else// }//if// break; }//case// default: { throw new RuntimeException("Shouldn't get here."); }//default// }//switch// }//if// text = isShowingShadowText ? null : text == null || text.length() == 0 ? null : text; doesServerValueMatchDisplayedValue = Comparator.equals(text, serverModelValue != null && numberFormat != null ? numberFormat.format(serverModelValue) : null); //Comparator.equals(getServerModelValue(), displayedValue) doesNewValueMatchDisplayedValue = Comparator.equals(text, value != null && numberFormat != null ? numberFormat.format(value) : null); //Comparator.equals(value, displayedValue) //Check to see if the user has altered the value. If the user has then don't update the control with the new value, instead notify the user of the underlying model change.// if(flags != ISingleResourceAssociationChangeListener.FLAG_CONTAINING_MODEL_CHANGED && !doesServerValueMatchDisplayedValue && !doesNewValueMatchDisplayedValue) { setServerModelValue((Number) value); //Notify the user of the underlying model change.// internalOnModelExternallyChanged(false, value); }//if// else { //Remove any old notification.// removeDecoration(originalValueChangedDecoration); originalValueChangedDecoration = null; //Set the server's model value.// setServerModelValue((Number) value); //Update the control.// internalRefreshValue((Number) getServerModelValue()); }//else// }//refreshValue()// /** * Refreshes the displayed value. * @param value The new value to be displayed. */ protected void internalRefreshValue(Number value) { DecimalFormat numberFormat = getNumberFormat(); //Ensure the correct foreground color is being used.// internalSetFormatForegroundColor(value != null && value.longValue() < 0 ? getNegativeForegroundColor() : null); //Set the text.// internalSetText(value != null && numberFormat != null ? numberFormat.format(value) : null); }//refreshValue()// /** * Sets the original value which should generate a change notification. * @param isCleared Whether the value has been cleared, and the passed value should be ignored. * @param value The value set in the model as the new original value. */ protected void internalOnModelExternallyChanged(boolean isCleared, Object originalValue) { //Clear the old decoration.// removeDecoration(originalValueChangedDecoration); originalValueChangedDecoration = null; if(!isCleared && !isReadOnly) { //TODO: Use the original value. addDecoration(originalValueChangedDecoration = getChangeControlDecoration()); }//if// }//internalOnModelExternallyChanged()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#getModelValue() */ protected Object getModelValue() { return getModelValue(extractNumber()); }//getModelValue()// /** * Gets the model value for the given field text. * @param text The field text. * @return The model number. */ protected Number getModelValue(String text) { Number value = null; if(text != null) { DecimalFormat numberFormat = getNumberFormat(); BigDecimal bigDecimal = new BigDecimal(text); if(getMaxNumber() != null && bigDecimal.compareTo(getMaxNumber()) > 0) { bigDecimal = getMaxNumber(); }//if// else if(getMinNumber() != null && bigDecimal.compareTo(getMinNumber()) < 0) { bigDecimal = getMinNumber(); }//else if// //We should not need to catch exceptions thrown by data conversion because the input verification method should make sure they don't occur.// switch(modelType) { case DATA_TYPE_BYTE: { value = new Byte(bigDecimal.byteValue()); if(numberFormat.getMultiplier() != 1) { value = new Byte((byte) (((Byte) value).byteValue() / ((byte) numberFormat.getMultiplier()))); }//if// break; }//case// case DATA_TYPE_SHORT: { value = new Short(bigDecimal.shortValue()); if(numberFormat.getMultiplier() != 1) { value = new Short((short) (((Short) value).shortValue() / ((short) numberFormat.getMultiplier()))); }//if// break; }//case// case DATA_TYPE_INTEGER: { value = new Integer(bigDecimal.intValue()); if(numberFormat.getMultiplier() != 1) { value = new Integer(((Integer) value).intValue() / ((int) numberFormat.getMultiplier())); }//if// break; }//case// case DATA_TYPE_LONG: { value = new Long(bigDecimal.longValue()); if(numberFormat.getMultiplier() != 1) { value = new Long(((Long) value).longValue() / ((long) numberFormat.getMultiplier())); }//if// break; }//case// case DATA_TYPE_FLOAT: { value = new Float(bigDecimal.floatValue()); if(numberFormat.getMultiplier() != 1) { value = new Float(((Float) value).floatValue() / ((float) numberFormat.getMultiplier())); }//if// break; }//case// case DATA_TYPE_DOUBLE: { value = new Double(bigDecimal.doubleValue()); if(numberFormat.getMultiplier() != 1) { value = new Double(((Double) value).doubleValue() / ((double) numberFormat.getMultiplier())); }//if// break; }//case// case DATA_TYPE_BIG_DECIMAL: { value = bigDecimal; if(numberFormat.getMultiplier() != 1) { value = ((BigDecimal) value).divide(new BigDecimal(numberFormat.getMultiplier())); }//if// break; }//case// default: { throw new RuntimeException("Should never get here."); }//default// }//switch// }//if// return value; }//getModelValue()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#setModelValue(java.lang.Object) */ protected void setModelValue(Object value) { sendMessage(MESSAGE_SYNCHRONIZE_VALUE, (Number) value, null, -1); }//setModelValue()// /** * Extracts the number text from all the formatting. * @return The text containing only english standard formatting accepted by the Double, BigDecimal, and other Number derrived classes. */ protected String extractNumber() { String result = null; String newText = getSwtText().getText(); if(isShowingShadowText) { result = defaultValue; }//if// else if((newText != null) && (newText.length() > 0)) { StringBuffer numberOnly = new StringBuffer(newText.length()); boolean decimalFound = false; boolean digitFound = false; boolean negativePrefix = newText.startsWith(getNumberFormat().getNegativePrefix()); boolean negativeSuffix = newText.endsWith(getNumberFormat().getNegativeSuffix()); boolean positivePrefix = newText.startsWith(getNumberFormat().getPositivePrefix()); boolean positiveSuffix = newText.endsWith(getNumberFormat().getPositiveSuffix()); int exponentIndex; String exponentText = null; //Replace any positive or negative prefix and/or suffix with a minus sign prefix or no prefix to make number detection simpler.// if(negativePrefix) { newText = minusSign + newText.substring(negativePrefix ? getNumberFormat().getNegativePrefix().length() : 0, newText.length() - (negativeSuffix ? getNumberFormat().getNegativeSuffix().length() : 0)); }//if// else if(positivePrefix) { newText = newText.substring(positivePrefix ? getNumberFormat().getPositivePrefix().length() : 0, newText.length() - (positiveSuffix ? getNumberFormat().getPositiveSuffix().length() : 0)); }//else if// //NOTE: It is assumed that formatting will either always show the exponent text or never show it, thus no need to identify the user typing a partial exponent separator.// //NOTE: This may require some testing to get right.// //Separate the exponent portion of the number.// if((exponentIndex = newText.indexOf(exponentSeparator)) != -1) { exponentText = newText.substring(exponentIndex + exponentSeparator.length()); newText = newText.substring(0, exponentIndex); }//if// //Identify the actual number sans formatting.// for(int index = 0; index < newText.length(); index++) { char ch = newText.charAt(index); boolean isDecimal = ch == decimal; //Gather the characters that make up the number and ignore all other characters.// if((Character.isDigit(ch)) || ((index == 0) && (ch == minusSign)) || (allowDecimal && isDecimal && !decimalFound)) { if(ch == minusSign) { numberOnly.append('-'); }//if// else if(ch == decimal) { numberOnly.append('.'); }//else if// else { numberOnly.append(ch); digitFound = true; }//else// if(isDecimal) { decimalFound = true; }//if// }//if// }//for// //Replace the exponent text with 'e' (what BigDecimal wants) and gather the unformatted numbers following the exponent.// if(exponentText != null) { boolean negativeExponent = false; if(exponentText.length() > 0 && exponentText.charAt(0) == minusSign) { negativeExponent = true; exponentText = exponentText.substring(1); }//if// if(exponentText.length() > 0) { numberOnly.append('e'); if(negativeExponent) { numberOnly.append('-'); }//if// //Identify the actual number sans formatting.// for(int index = 0; index < exponentText.length(); index++) { char ch = exponentText.charAt(index); //Gather the characters that make up the number and ignore all other characters.// if(Character.isDigit(ch)) { numberOnly.append(ch); }//if// }//for// }//if// }//if// //Ignore the case where a negative number or decimal without digits has been typed, or nothing has been typed.// if(digitFound) { try { result = numberOnly.toString(); }//try// catch(Throwable e) { Debug.log(e); result = defaultValue; }//catch// }//if// else { result = defaultValue; }//else// }//else if// return result; }//extractNumber()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#verifyText(org.eclipse.swt.events.VerifyEvent) */ protected void verifyText(VerifyEvent event) { //Note: event may be null if we are forcing a verification.// if(!isShowingShadowText) { String firstPart = event != null ? event.start == 0 ? "" : getSwtText().getText().substring(0, event.start) : getSwtText().getText(); StringBuffer newPart = event != null && event.text != null && event.text.length() > 0 ? new StringBuffer(event.text) : null; String lastPart = event != null ? event.end == getSwtText().getText().length() ? "" : getSwtText().getText().substring(event.end) : ""; //Validate the characters in the added text. Note that this just performs an initial check, the real validation is in the processText method.// if(newPart != null) { if(newPart.length() == 1) { char ch = newPart.charAt(0); //TODO: Should we allow the grouping separator? if(!allowNegative && firstPart.length() == 0 && ch == minusSign) { //We will only strip out the negative character (for now) if it is not allowed and it is being placed at the start of the number and it is the only addition - otherwise we will leave it up to the processing to determine what to do.// event.doit = false; }//if// else if(!Character.isDigit(ch) && ch != minusSign) { //Don't allow a non-numeric character to be typed other than a valid number symbol.// event.doit = false; }//else if// }//if// else if(newPart.length() > 1) { //This is a cut and paste job - we will handle it by checking for invalid characters which will invalidate the whole operation.// //Check for any illegal characters and cancel the event if found.// for(int index = 0; event.doit && index < newPart.length(); index++) { char next = newPart.charAt(index); boolean isDigit = Character.isDigit(next); boolean isSign = allowNegative && next == minusSign && index == 0 && firstPart.length() == 0; //Check the next character to see if it is illegal.// if(!isDigit && !isSign) { if(next == groupSeparator) { newPart.deleteCharAt(index); index--; }//if// else { //TODO: Should we just extract the character that is not valid, or really negate the whole change? event.doit = false; }//else// }//if// }//for// }//else if// }//if// if(event.doit && (getRealtime() || !getSwtText().isFocusControl())) { int removedCharacterCount = event != null ? event.end - event.start : 0; int caretPosition = getSwtText().getCaretPosition(); String oldText = event == null ? getSwtText().getText() : firstPart + (newPart == null ? "" : newPart.toString()) + lastPart; String truncatedOldText = event == null ? getSwtText().getText() : firstPart + lastPart; //Ensure the caret position is at the beginning of the removed section.// if(removedCharacterCount > 0) { caretPosition = getSwtText().getSelection().x; }//if// caretPosition = caretPosition + (newPart == null ? 0 : newPart.length()); event.doit = processText(caretPosition, oldText, truncatedOldText, newPart == null ? "" : newPart.toString(), true); }//if// }//if// }//verifyText()// /** * Processes the text in the field and first determines if we have a valid number, then determines whether the number should be formatted and the text replaced. */ protected void processText() { String text = getSwtText().getText(); processText(0, text, text, "", false); }//processText()// /** * Processes the text in the field and first determines if we have a valid number, then determines whether the number should be formatted and the text replaced. * @param caretPosition The caret position within the truncated text. This should be zero if not known. * @param inputText The text in the field after the change is made. This should never be null. * @param truncatedText The text currently in the field, minus the any part removed by the change in the text (but without any added text). This will never be null. * @param addedText The text being added or inserted in the truncatedText. This will never be null. * @param hasFocus Whether the control currently is being used by the viewer. * @return Whether the text could be processed. This will only be false if the caller should try to cancel any changes being made to the text. */ protected boolean processText(int caretPosition, String inputText, String truncatedText, String addedText, boolean hasFocus) { boolean result = true; if(!isShowingShadowText) { StringBuffer numberOnly = new StringBuffer(inputText.length()); int newCaretPosition = 0; int digitCount = 0; int trailingZeroCount = 0; //The count of zero's trailing the decimal (and before any exponent).// boolean isNumberNegative = false; {//Process the text so that we have just the relevant characters to form a BigDecimal. Also collect metadata along the way.// boolean hasNegativePrefix = negativePrefix.length() > 0 ? inputText.startsWith(negativePrefix) : false; boolean hasNegativeSuffix = negativeSuffix.length() > 0 ? inputText.endsWith(negativeSuffix) : false; boolean hasPositivePrefix = positivePrefix.length() > 0 ? inputText.startsWith(positivePrefix) : false; boolean hasPositiveSuffix = positiveSuffix.length() > 0 ? inputText.endsWith(positiveSuffix) : false; String newText = inputText; //Replace any positive or negative prefix and/or suffix with a minus sign prefix or no prefix to make number detection simpler.// if(hasNegativePrefix || hasNegativeSuffix) { newText = minusSign + newText.substring(hasNegativePrefix ? negativePrefix.length() : 0, newText.length() - (hasNegativeSuffix ? negativeSuffix.length() : 0)); caretPosition -= negativePrefix.length(); isNumberNegative = true; }//if// else if(hasPositivePrefix || hasPositiveSuffix) { newText = newText.substring(hasPositivePrefix ? positivePrefix.length() : 0, newText.length() - (hasPositiveSuffix ? positiveSuffix.length() : 0)); caretPosition -= positivePrefix.length(); }//else if// //Begin using the new caret position so that we don't adjust if a removed character is after the caret position in the truncated text.// newCaretPosition = caretPosition < 0 ? 0 : caretPosition; //Identify the actual number sans formatting.// for(int index = 0; index < newText.length(); index++) { char ch = newText.charAt(index); //Gather the characters that make up the number and ignore all other characters.// if((Character.isDigit(ch)) || ((index == 0) && (ch == minusSign))) { if(ch == minusSign) { numberOnly.append('-'); }//if// else { digitCount++; numberOnly.append(ch); if(ch == zero) { //Count any trailing zero's which must be preserved as long as the user is typing in the number.// trailingZeroCount++; }//else if// else { //Clear the trailing zero count since we found a non-zero digit following the decimal.// trailingZeroCount = 0; }//else// }//else// }//if// else if(index < caretPosition) { newCaretPosition--; }//else if// }//for// }//block// try { isModifying = false; //Note: The variable numberOnly contains US standard numeric characters only.// //Ignore the case where a negative number without digits has been typed, or nothing has been typed.// if(digitCount > 0) { BigDecimal newNumber = null; StringBuffer text = null; //Convert the text to a BigDecimal for examination.// newNumber = new BigDecimal(numberOnly.toString()); //Check to see if the new value is within the valid numeric range.// //If not within the range then set the isModifying flag, or change the number to be within the range.// if((getMinNumber() != null) && (newNumber.compareTo(getMinNumber()) == IComparator.LESS_THAN)) { //Use the isModifying flag if the control has focus since the user may be trying to type a valid number.// if(hasFocus) { isModifying = true; }//if// else { newNumber = getMinNumber(); }//else// }//if// else if((getMaxNumber() != null) && (newNumber.compareTo(getMaxNumber()) == IComparator.GREATER_THAN)) { //Use the isModifying flag if the control has focus since the user may be trying to type a valid number.// if(hasFocus) { isModifying = true; }//if// else { newNumber = getMaxNumber(); }//else// }//else if// //Adjust the number so the multiplier doesn't screw with the output.// if(getNumberFormat().getMultiplier() != 1) { newNumber = newNumber.divide(new BigDecimal(getNumberFormat().getMultiplier())); }//if// //Convert the BigDecimal back into text with all the extra formatting.// text = new StringBuffer(getNumberFormat().format(newNumber)); //If the text has changed then apply the changes.// if(!inputText.equals(text)) { int startIndex = 0; int suffixLength = 0; int prefixLength = 0; if(isNumberNegative) { newCaretPosition += (negativePrefix.length()); startIndex += negativePrefix.length(); prefixLength = negativePrefix.length(); suffixLength = negativeSuffix.length(); if(getNegativeForegroundColor() != null) { internalSetFormatForegroundColor(getNegativeForegroundColor()); }//if// }//if// else { newCaretPosition += positivePrefix.length(); startIndex += positivePrefix.length(); prefixLength = positivePrefix.length(); suffixLength = positiveSuffix.length(); internalSetFormatForegroundColor(null); }//else// //Since the formatting may have added extra characters before the number we must identify how many it may have added.// if(getNumberFormat().getMinimumIntegerDigits() > digitCount) { newCaretPosition += getNumberFormat().getMinimumIntegerDigits() - digitCount; }//if// //Search for separators and add them into the caret position.// for(int index = 0; index < text.length() && index < newCaretPosition; index++) { if(text.charAt(index) == groupSeparator) { newCaretPosition++; }//if// }//for// //Ensure we don't exceed the size of the number and bleed into the suffix.// if(newCaretPosition > text.length() - suffixLength) { newCaretPosition = text.length() - suffixLength; }//if// //Compare with the previous text since some of the additional text may have been used.// if(!getSwtText().getText().equals(text.toString())) { try { isRefreshingText = true; getSwtText().setText(text.toString()); getSwtText().setSelection(newCaretPosition); }//try// finally { isRefreshingText = false; }//finally// //Force the modify code to run so that the new text can be auto-synchronized.// modifyText(null); }//if// else { //Ensure that the cursor doesn't bleed into the suffix, or if it was there to begin with then move it back to the end of the number.// if(caretPosition > text.length() - suffixLength - prefixLength) { caretPosition = text.length() - suffixLength - prefixLength; }//if// getSwtText().setSelection(caretPosition + prefixLength); }//else// result = false; }//if// }//if// else { //A number could not be identified, but valid pieces of a number may have been identified.// internalSetFormatForegroundColor(null); //Ensure that the field is either empty or contains digit, or decimal characters only.// if(numberOnly.length() != 0) { boolean hasSign = ((numberOnly.length() > 0) && (numberOnly.charAt(0) == '-')); String text; if(hasSign) { text = getNumberFormat().getNegativePrefix() + getNumberFormat().getNegativeSuffix(); newCaretPosition += getNumberFormat().getNegativePrefix().length(); }//if// else { text = getNumberFormat().getPositivePrefix() + getNumberFormat().getPositiveSuffix(); newCaretPosition += getNumberFormat().getPositivePrefix().length(); }//else// if(!inputText.equals(text)) { try { isRefreshingText = true; getSwtText().setText(text); getSwtText().setSelection(newCaretPosition); }//try// finally { isRefreshingText = false; }//finally// result = false; }//if// }//if// else { if(getSwtText().getText().length() != 0) { //Ensure the text field is empty.// try { isRefreshingText = true; getSwtText().setText(""); getSwtText().setSelection(0); }//try// finally { isRefreshingText = false; }//finally// }//if// result = false; }//else// }//else// }//try// catch(Throwable e) { Debug.log(e); result = false; }//catch// }//if// return result; }//processText()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#focusLost(org.eclipse.swt.events.FocusEvent) */ protected void focusLost(FocusEvent event) { if(!getRealtime() || isModifying) { isModifying = false; processText(); }//if// }//focusLost()// }//IntegerFormat// public class FloatFormat extends IntegerFormat implements IFloatFormat { /** The maximum number of fraction digits to allow. */ private Integer maxFractionDigits = null; /** The minimum number of fraction digits to display. */ private Integer minFractionDigits = null; /** The multiplier used when formatting the percent. */ private Integer multiplier = null; public FloatFormat() { setModelType(DATA_TYPE_FLOAT); }//FloatFormat()// /** * Gets the maximum number of fraction digits. * @return The maximum number of fraction digits, or null to use the default value. */ protected Integer getMaxFractionDigits() { return maxFractionDigits; }//getMaxFractionDigits()// /** * Gets the minimum number of fraction digits. * @return The minimum number of fraction digits, or null to use the default value. */ protected Integer getMinFractionDigits() { return minFractionDigits; }//getMinFractionDigits()// /** * Gets the percent's multiplier. * @return The multiplier used for formatting the percent. */ protected Integer getMultiplier() { return multiplier; }//getMultiplier()// /** * Creates the new number format to be used. * @return The uninitialized number format used to convert numbers to/from text. */ public DecimalFormat createNumberFormat() { Locale locale = getLocale(); String pattern = getFormatPattern(); DecimalFormat result = (DecimalFormat) (locale != null ? DecimalFormat.getNumberInstance(locale) : DecimalFormat.getNumberInstance()); if(pattern != null) { result.applyPattern(pattern); }//if// return result; }//createNumberFormat()// /** * Initializes the number format. * @param numberFormat The number format used to convert numbers to/from text. */ public void initializeNumberFormat(DecimalFormat numberFormat) { if(getMaxFractionDigits() != null) { numberFormat.setMaximumFractionDigits(getMaxFractionDigits().intValue()); }//if// if(getMinFractionDigits() != null) { numberFormat.setMinimumFractionDigits(getMinFractionDigits().intValue()); }//if// if(getMultiplier() != null) { numberFormat.setMultiplier(getMultiplier().intValue()); }//if// super.initializeNumberFormat(numberFormat); }//initializeNumberFormat()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.IntegerFormat#verifyText(org.eclipse.swt.events.VerifyEvent) */ public Object internalProcessMessage(ViewMessage viewMessage) { Object result = null; switch(viewMessage.getMessageNumber()) { case MESSAGE_SET_MAX_FRACTION_DIGITS: { maxFractionDigits = (Integer) viewMessage.getMessageData(); break; }//case// case MESSAGE_SET_MIN_FRACTION_DIGITS: { minFractionDigits = (Integer) viewMessage.getMessageData(); break; }//case// case MESSAGE_SET_MULTIPLIER: { multiplier = (Integer) viewMessage.getMessageData(); break; }//case// default: { result = super.internalProcessMessage(viewMessage); }//default// }//switch// return result; }//internalProcessMessage()// /* (non-Javadoc) * @see com.foundation.view.swt.TextField.Format#verifyText(org.eclipse.swt.events.VerifyEvent) */ protected void verifyText(VerifyEvent event) { //Note: event may be null if we are forcing a verification.// if(!isShowingShadowText) { String firstPart = event != null ? event.start == 0 ? "" : getSwtText().getText().substring(0, event.start) : getSwtText().getText(); StringBuffer newPart = event != null && event.text != null && event.text.length() > 0 ? new StringBuffer(event.text) : null; String lastPart = event != null ? event.end == getSwtText().getText().length() ? "" : getSwtText().getText().substring(event.end) : ""; //Validate the characters in the added text. Note that this just performs an initial check, the real validation is in the processText method.// if(newPart != null) { if(newPart.length() == 1) { char ch = newPart.charAt(0); //TODO: Should we allow the grouping separator? if(!getAllowNegative() && firstPart.length() == 0 && ch == minusSign) { //We will only strip out the negative character (for now) if it is not allowed and it is being placed at the start of the number and it is the only addition - otherwise we will leave it up to the processing to determine what to do.// event.doit = false; }//if// else if(!getAllowDecimal() && ch == decimal) { //Don't allow a decimal to be typed if that is all that was added.// event.doit = false; }//else if// else if(firstPart.length() > 0 && exponentSeparator != null && Character.toLowerCase(exponentSeparator.charAt(0)) == Character.toLowerCase(ch) && lastPart.length() == 0) { //Don't allow an exponent at the beginning of the number, or anywhere but the end of the number.// newPart.deleteCharAt(0); newPart.append(exponentSeparator); event.text = newPart.toString(); }//else if// else if(!Character.isDigit(ch) && ch != minusSign && ch != decimal) { //Don't allow a non-numeric character to be typed other than a valid number symbol.// event.doit = false; }//else if// }//if// else if(newPart.length() > 1) { //This is a cut and paste job - we will handle it by checking for invalid characters which will invalidate the whole operation.// //Check for any illegal characters and cancel the event if found.// for(int index = 0; event.doit && index < newPart.length(); index++) { char next = newPart.charAt(index); boolean isDigit = Character.isDigit(next); boolean isDecimal = getAllowDecimal() && next == decimal; boolean isSign = getAllowNegative() && next == minusSign && index == 0 && firstPart.length() == 0; boolean isExponent = next == exponentSeparator.charAt(0) && newPart.length() - index >= exponentSeparator.length() && newPart.substring(index, index + exponentSeparator.length()).equals(exponentSeparator); //boolean isGroupingSeparator = group != null && group.booleanValue() && next == groupSeparator; //Check the next character to see if it is illegal.// if(!isDigit && !isDecimal && !isSign && !isExponent) { if(next == groupSeparator) { newPart.deleteCharAt(index); index--; }//if// else { //TODO: Should we just extract the character that is not valid, or really negate the whole change? event.doit = false; }//else// }//if// else if(isExponent && exponentSeparator.length() > 1) { //Jump past the extra exponent separator characters if we did find an exponent.// index += exponentSeparator.length() - 1; }//else if// }//for// }//else if// }//if// if(event.doit && (getRealtime() || !getSwtText().isFocusControl())) { int removedCharacterCount = event != null ? event.end - event.start : 0; int caretPosition = getSwtText().getCaretPosition(); String oldText = event == null ? getSwtText().getText() : firstPart + (newPart == null ? "" : newPart.toString()) + lastPart; String truncatedOldText = event == null ? getSwtText().getText() : firstPart + lastPart; //Adjust the caret position since the caret could be on either side of the removed characters.// if(removedCharacterCount > 0) { caretPosition = firstPart.length(); }//if// caretPosition = caretPosition + (newPart == null ? 0 : newPart.length()); event.doit = processText(caretPosition, oldText, truncatedOldText, newPart == null ? "" : newPart.toString(), true); }//if// }//if// }//verifyText()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.IntegerFormat#processText(int, java.lang.String, java.lang.String, java.lang.String, boolean) */ protected boolean processText(int caretPosition, String inputText, String truncatedText, String addedText, boolean hasFocus) { boolean result = true; if(!isShowingShadowText) { StringBuffer numberOnly = new StringBuffer(inputText.length()); int newCaretPosition = 0; boolean endsWithDecimal = false; boolean endsWithExponent = false; boolean negativeExponent = false; boolean digitFound = false; int preDecimalDigitCount = 0; int trailingZeroCount = 0; //The count of zero's trailing the decimal (and before any exponent).// boolean isNumberNegative = false; int caretOffsetFromDecimal = -1; int caretOffsetFromExponent = -1; {//Process the text so that we have just the relevant characters to form a BigDecimal. Also collect metadata along the way.// boolean hasNegativePrefix = negativePrefix.length() > 0 ? inputText.startsWith(negativePrefix) : false; boolean hasNegativeSuffix = negativeSuffix.length() > 0 ? inputText.endsWith(negativeSuffix) : false; boolean hasPositivePrefix = positivePrefix.length() > 0 ? inputText.startsWith(positivePrefix) : false; boolean hasPositiveSuffix = positiveSuffix.length() > 0 ? inputText.endsWith(positiveSuffix) : false; boolean decimalFound = false; int exponentIndex; String exponentText = null; String newText = inputText; String actualExponentSeparator = null; //Replace any positive or negative prefix and/or suffix with a minus sign prefix or no prefix to make number detection simpler.// if((hasNegativePrefix && !hasPositivePrefix) || (hasNegativeSuffix && !hasPositiveSuffix)) { newText = minusSign + newText.substring(hasNegativePrefix ? negativePrefix.length() : 0, newText.length() - (hasNegativeSuffix ? negativeSuffix.length() : 0)); caretPosition -= negativePrefix.length(); isNumberNegative = true; }//if// else if((hasPositivePrefix && !hasNegativePrefix) || (hasPositiveSuffix && !hasNegativeSuffix)) { newText = newText.substring(hasPositivePrefix ? positivePrefix.length() : 0, newText.length() - (hasPositiveSuffix ? positiveSuffix.length() : 0)); caretPosition -= positivePrefix.length(); }//else if// //Begin using the new caret position so that we don't adjust if a removed character is after the caret position in the truncated text.// newCaretPosition = caretPosition < 0 ? 0 : caretPosition; //Test to see if there is an exponent separator and what it is (we allow the first character to be typed which will be interperated as the full exponent string).// if((exponentIndex = newText.indexOf(exponentSeparator)) != -1) { actualExponentSeparator = exponentSeparator; }//if// else if((exponentIndex = newText.indexOf(Character.toUpperCase(exponentSeparator.charAt(0)))) != -1) { actualExponentSeparator = "" + Character.toUpperCase(exponentSeparator.charAt(0)); }//else if// else if((exponentIndex = newText.indexOf(Character.toLowerCase(exponentSeparator.charAt(0)))) != -1) { actualExponentSeparator = "" + Character.toLowerCase(exponentSeparator.charAt(0)); }//else if// //Separate the exponent portion of the number.// if(exponentIndex != -1) { exponentText = newText.substring(exponentIndex + actualExponentSeparator.length()); newText = newText.substring(0, exponentIndex); if(caretPosition >= exponentIndex + actualExponentSeparator.length()) { caretOffsetFromExponent = caretPosition - exponentIndex - actualExponentSeparator.length(); }//if// }//if// //Identify the actual number sans formatting.// for(int index = 0; index < newText.length(); index++) { char ch = newText.charAt(index); boolean isDecimal = ch == decimal; //Gather the characters that make up the number and ignore all other characters.// if((Character.isDigit(ch)) || ((index == 0) && (ch == minusSign)) || (getAllowDecimal() && isDecimal && !decimalFound)) { if(ch == minusSign) { numberOnly.append('-'); isNumberNegative = true; }//if// else if(ch == decimal) { numberOnly.append('.'); //Calculate the caret offset from the decimal if the caret is after the decimal and it is not after the exponent.// if(caretOffsetFromExponent == -1 && newCaretPosition + (isNumberNegative ? 1 : 0) > numberOnly.length()) { caretOffsetFromDecimal = newCaretPosition - numberOnly.length() + (isNumberNegative ? 1 : 0); }//if// }//else if// else { numberOnly.append(ch); digitFound = true; if(!decimalFound) { preDecimalDigitCount++; }//if// else if(ch == zero) { //Count any trailing zero's which must be preserved as long as the user is typing in the number.// trailingZeroCount++; }//else if// else { //Clear the trailing zero count since we found a non-zero digit following the decimal.// trailingZeroCount = 0; }//else// }//else// if(isDecimal) { decimalFound = true; if(index + 1 == newText.length()) { endsWithDecimal = true; }//if// }//if// }//if// else if(index < caretPosition) { newCaretPosition--; }//else if// }//for// //Replace the exponent text with 'e' (what BigDecimal wants) and gather the unformatted numbers following the exponent.// if(exponentText != null) { if(exponentText.length() > 0 && exponentText.charAt(0) == minusSign) { negativeExponent = true; exponentText = exponentText.substring(1); }//if// if(exponentText.length() == 0) { endsWithExponent = true; }//if// else { numberOnly.append('e'); if(negativeExponent) { numberOnly.append('-'); }//if// //Identify the actual number sans formatting.// for(int index = 0; index < exponentText.length(); index++) { char ch = exponentText.charAt(index); //Gather the characters that make up the number and ignore all other characters.// if(Character.isDigit(ch)) { numberOnly.append(ch); }//if// else if(numberOnly.length() + index + exponentSeparator.length() < caretPosition) { newCaretPosition--; }//else if// }//for// }//else// }//if// }//block// try { isModifying = false; //Note: The variable numberOnly contains US standard numeric characters only.// //Ignore the case where a negative number without digits has been typed, or nothing has been typed.// if(digitFound) { BigDecimal newNumber = null; StringBuffer text = null; //Convert the text to a BigDecimal for examination.// newNumber = new BigDecimal(numberOnly.toString()); //Check to see if the new value is within the valid numeric range.// //If not within the range then set the isModifying flag, or change the number to be within the range.// if((getMinNumber() != null) && (newNumber.compareTo(getMinNumber()) == IComparator.LESS_THAN)) { //Use the isModifying flag if the control has focus since the user may be trying to type a valid number.// if(hasFocus) { isModifying = true; }//if// else { newNumber = getMinNumber(); }//else// }//if// else if((getMaxNumber() != null) && (newNumber.compareTo(getMaxNumber()) == IComparator.GREATER_THAN)) { //Use the isModifying flag if the control has focus since the user may be trying to type a valid number.// if(hasFocus) { isModifying = true; }//if// else { newNumber = getMaxNumber(); }//else// }//else if// //Adjust the number so the multiplier doesn't screw with the output.// if(getNumberFormat().getMultiplier() != 1) { newNumber = newNumber.divide(new BigDecimal(getNumberFormat().getMultiplier())); }//if// //Convert the BigDecimal back into text with all the extra formatting.// text = new StringBuffer(getNumberFormat().format(newNumber)); //Put the decimal, exponent, or trailing zero's back at the end of the formatted number since the formatting will strip it.// if(endsWithDecimal) { //Verify we don't have a decimal in there already. We might if the formatting requires one.// if(text.indexOf("" + decimal) == -1) { int index = text.indexOf(exponentSeparator); boolean placed = false; if(index == -1) { index = text.length(); }//if// //Search for the last digit from the end of the string or beginning of the exponent.// while(!placed) { char next = text.charAt(--index); //If the character is a digit then we add the decimal after the character.// if(Character.isDigit(next)) { text.insert(index + 1, decimal); placed = true; }//if// }//while// }//if// }//if// else if(trailingZeroCount > 0) { int exponentIndex = text.indexOf(exponentSeparator); int decimalIndex = text.indexOf("" + decimal); boolean placed = false; if(exponentIndex == -1) { exponentIndex = text.length(); }//if// if(decimalIndex > exponentIndex) { decimalIndex = -1; }//if// if(decimalIndex == -1) { //Search backward from the exponent until we find a numeric character, then add the decimal and trailing zeros.// for(int index = exponentIndex - 1; !placed; index--) { char next = text.charAt(index); if(Character.isDigit(next)) { int zeroCount = getMaxFractionDigits() != null ? Math.min(trailingZeroCount, getMaxFractionDigits().intValue()) : trailingZeroCount; text.insert(++index, decimal); for(int count = 0; count < zeroCount; count++) { text.insert(++index, zero); }//for// placed = true; }//if// }//for// }//if// else { int digitCount = 0; int zeroCount; int existingZeroCount = 0; char next; //Search for the last numeric character after the decimal, and count the number of trailing zeros already there.// while(++decimalIndex < text.length() && Character.isDigit((next = text.charAt(decimalIndex)))) { digitCount++; if(next == zero) { existingZeroCount++; }//if// else { existingZeroCount = 0; }//else// }//while// //Update the trailing zero count to handle trailing zeros added by the formatter.// zeroCount = trailingZeroCount - existingZeroCount; //Calculate the number of zeros to add being careful not to exceed the maxium number of numbers following the decimal.// zeroCount = Math.min(zeroCount, getNumberFormat().getMaximumFractionDigits() - digitCount); //Add the zeros to the text.// for(int count = 0; count < zeroCount; count++) { text.insert(decimalIndex + count, zero); }//for// }//else// }//else if// else if(endsWithExponent) { //Verify that the formatter didn't already add the exponent.// if(text.indexOf(exponentSeparator) == -1) { int index = text.length(); boolean placed = false; //Search for the last digit.// while(!placed) { char next = text.charAt(--index); //If the character is a digit then we add the exponent after the character.// if(Character.isDigit(next)) { text.insert(index + 1, exponentSeparator + (negativeExponent ? "" + minusSign : "")); placed = true; }//if// }//while// }//if// }//else if// //If the text has changed then apply the changes.// if(!inputText.equals(text.toString())) { int startIndex = 0; int prefixLength = 0; int suffixLength = 0; if(isNumberNegative) { newCaretPosition += negativePrefix.length(); startIndex += negativePrefix.length(); prefixLength = negativePrefix.length(); suffixLength = negativeSuffix.length(); if(getNegativeForegroundColor() != null) { internalSetFormatForegroundColor(getNegativeForegroundColor()); }//if// }//if// else { newCaretPosition += positivePrefix.length(); startIndex += positivePrefix.length(); prefixLength = positivePrefix.length(); suffixLength = positiveSuffix.length(); internalSetFormatForegroundColor(null); }//else// //Since the formatting may have added extra characters before the number we must identify how many it may have added.// if(getNumberFormat().getMinimumIntegerDigits() > preDecimalDigitCount) { newCaretPosition += getNumberFormat().getMinimumIntegerDigits() - preDecimalDigitCount; }//if// //Search for separators and add them into the caret position.// for(int index = 0; index < text.length() && index < newCaretPosition; index++) { if(text.charAt(index) == groupSeparator) { newCaretPosition++; }//if// }//for// //Compare with the previous text since some of the additional text may have been used.// if(!getSwtText().getText().equals(text.toString())) { try { isRefreshingText = true; getSwtText().setText(text.toString()); //Use the relative position of the caret if we have one.// if(caretOffsetFromExponent != -1) { int index = text.indexOf(exponentSeparator, prefixLength); //Index shouldn't ever be -1, but just in case.// if(index != -1) { newCaretPosition = index + caretOffsetFromExponent + exponentSeparator.length(); }//if// else { newCaretPosition = text.length() - suffixLength; }//else// }//if// else if(caretOffsetFromDecimal != -1) { int index = text.indexOf("" + decimal, prefixLength); //Index shouldn't ever be -1, but just in case.// if(index != -1) { newCaretPosition = index + caretOffsetFromDecimal + 1; }//if// else { newCaretPosition = text.length() - suffixLength; }//else// }//else if// //Ensure that the cursor doesn't bleed into the suffix, or if it was there to begin with then move it back to the end of the number.// if(newCaretPosition > text.length() - suffixLength) { newCaretPosition = text.length() - suffixLength; }//if// getSwtText().setSelection(newCaretPosition); }//try// finally { isRefreshingText = false; }//finally// //Force the modify code to run so that the new text can be auto-synchronized.// modifyText(null); }//if// else { //Ensure that the cursor doesn't bleed into the suffix, or if it was there to begin with then move it back to the end of the number.// if(caretPosition > text.length() - suffixLength - prefixLength) { caretPosition = text.length() - suffixLength - prefixLength; }//if// getSwtText().setSelection(caretPosition + prefixLength); }//else// result = false; }//if// }//if// else { //A number could not be identified, but valid pieces of a number may have been identified.// internalSetFormatForegroundColor(null); //Ensure that the field is either empty or contains sign, exponent, digit, or decimal characters only.// //Note: If the number isn't readable as a number then it will be assumed the user is not typing the exponent text. The exponent text is assumed to always be present in the formatted output if the number is readable.// if(numberOnly.length() != 0) { boolean hasDecimal = ((numberOnly.length() == 1) && (numberOnly.charAt(0) == '.')) || ((numberOnly.length() == 2) && (numberOnly.charAt(1) == '.')); boolean hasSign = ((numberOnly.length() > 0) && (numberOnly.charAt(0) == '-')); String text; if(hasSign) { if(hasDecimal) { text = getNumberFormat().getNegativePrefix() + decimal + getNumberFormat().getNegativeSuffix(); }//if// else { text = getNumberFormat().getNegativePrefix() + getNumberFormat().getNegativeSuffix(); }//else// newCaretPosition += getNumberFormat().getNegativePrefix().length(); }//if// else { if(hasDecimal) { text = getNumberFormat().getPositivePrefix() + decimal + getNumberFormat().getPositiveSuffix(); }//if// else { text = getNumberFormat().getPositivePrefix() + getNumberFormat().getPositiveSuffix(); }//else// newCaretPosition += getNumberFormat().getPositivePrefix().length(); }//else// if(!inputText.equals(text)) { try { isRefreshingText = true; getSwtText().setText(text); getSwtText().setSelection(newCaretPosition); }//try// finally { isRefreshingText = false; }//finally// result = false; }//if// }//if// else { if(getSwtText().getText().length() != 0) { //Ensure the text field is empty.// try { isRefreshingText = true; getSwtText().setText(""); getSwtText().setSelection(0); }//try// finally { isRefreshingText = false; }//finally// }//if// result = false; }//else// }//else// }//try// catch(Throwable e) { Debug.log(e); result = false; }//catch// }//if// return result; }//processText()// }//FloatFormat// public class PercentFormat extends FloatFormat implements IPercentFormat { public PercentFormat() { }//PercentFormat()// /** * Creates the new number format to be used. * @return The uninitialized number format used to convert numbers to/from text. */ public DecimalFormat createNumberFormat() { Locale locale = (Locale) getLocale(); String pattern = getFormatPattern(); DecimalFormat result = (DecimalFormat) (locale != null ? DecimalFormat.getPercentInstance(locale) : DecimalFormat.getPercentInstance()); if(pattern != null) { result.applyPattern(pattern); }//if// return result; }//createNumberFormat()// /** * Initializes the number format. * @param numberFormat The number format used to convert numbers to/from text. */ public void initializeNumberFormat(DecimalFormat numberFormat) { super.initializeNumberFormat(numberFormat); }//initializeNumberFormat()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#internalProcessMessage(com.foundation.tcv.view.ViewMessage) */ public Object internalProcessMessage(ViewMessage viewMessage) { Object result = null; switch(viewMessage.getMessageNumber()) { default: { result = super.internalProcessMessage(viewMessage); }//default// }//switch// return result; }//internalProcessMessage()// }//PercentFormat// public class CurrencyFormat extends FloatFormat implements ICurrencyFormat { public CurrencyFormat() { }//CurrencyFormat()// /** * Creates the new number format to be used. * @return The uninitialized number format used to convert numbers to/from text. */ public DecimalFormat createNumberFormat() { Locale locale = (Locale) getLocale(); String pattern = getFormatPattern(); DecimalFormat result = (DecimalFormat) (locale != null ? DecimalFormat.getCurrencyInstance(locale) : DecimalFormat.getCurrencyInstance()); if(pattern != null) { result.applyPattern(pattern); }//if// return result; }//createNumberFormat()// /** * Initializes the number format. * @param numberFormat The number format used to convert numbers to/from text. */ public void initializeNumberFormat(DecimalFormat numberFormat) { super.initializeNumberFormat(numberFormat); }//initializeNumberFormat()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TextField.Format#internalProcessMessage(com.foundation.tcv.view.ViewMessage) */ public Object internalProcessMessage(ViewMessage viewMessage) { Object result = null; switch(viewMessage.getMessageNumber()) { default: { result = super.internalProcessMessage(viewMessage); }//default// }//switch// return result; }//internalProcessMessage()// }//CurrencyFormat// /** * TextField constructor. */ public TextField() { super(); }//TextField()// /** * Gets the SWT text that represents this text field. * @return The SWT text providing visualization for this text field. */ public org.eclipse.swt.widgets.Text getSwtText() { return (org.eclipse.swt.widgets.Text) getSwtControl(); }//getSwtText()// /** * Gets the format used by the text field to format and interperate the data. * @return The formatting used. */ public Format getFormat() { return format; }//getFormat()// /** * Sets the shadow text's color (defaults if null) * @param shadowTextColor The color used for the text foreground when displaying the shadow text. */ public void setShadowTextColor(JefColor shadowTextColor) { if(shadowTextForegroundSwtColor != null) { destroyColor(shadowTextForegroundSwtColor); shadowTextForegroundSwtColor = null; }//if// if(shadowTextColor != null) { shadowTextForegroundSwtColor = createColor(shadowTextColor); }//if// if(isShowingShadowText) { controlUpdateForegroundColor(); }//if// }//setShadowTextColor()// /** * Determines whether to select the contents of the field when the control is given focus. * @return Whether to select all when provided focus. */ public boolean getSelectOnFocus() { return selectOnFocus; }//getSelectOnFocus()// /** * Determines whether to select the contents of the field when the control is given focus. * @param selectOnFocus Whether to select all when provided focus. */ public void setSelectOnFocus(boolean selectOnFocus) { this.selectOnFocus = selectOnFocus; }//setSelectOnFocus()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.AbstractComponent#internalViewInitialize() */ protected void internalViewInitialize() { getSwtText().addModifyListener(this); getSwtText().addVerifyListener(this); getSwtText().addFocusListener(this); getSwtText().addKeyListener(this); getShell().addShellListener(this); format.internalViewInitialize(); super.internalViewInitialize(); }//internalViewInitialize()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.AbstractComponent#internalViewRelease() */ protected void internalViewRelease() { if((getSwtText() != null) && (!getSwtText().isDisposed())) { getSwtText().removeModifyListener(this); getSwtText().removeVerifyListener(this); getSwtText().removeFocusListener(this); getSwtText().removeKeyListener(this); getShell().removeShellListener(this); }//if// synchronized(this) { if(autoSynchronizeValueTask != null) { removeTask(autoSynchronizeValueTask); autoSynchronizeValueTask = null; }//if// }//synchronized// format.internalViewRelease(); super.internalViewRelease(); }//internalViewRelease()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.AbstractComponent#internalViewSynchronize() */ protected void internalViewSynchronize() { super.internalViewSynchronize(); //Allow the format additional synchronization. Note that the value is always synchronized by the text field control.// format.internalViewSynchronize(); if(!isReadOnly) { if(!autoSynchronizeValue) { Object value = getFormat().getModelValue(); if(((value == null) && (serverValue != null)) || ((value != null) && (!(value.equals(serverValue))))) { //Clear the decoration.// removeDecoration(getFormat().originalValueChangedDecoration); getFormat().originalValueChangedDecoration = null; //Store the new server's model value.// getFormat().setServerModelValue(value); //Update the server.// format.setModelValue(value); serverValue = value; }//if// }//if// else { synchronized(this) { if(autoSynchronizeValueTask != null) { removeTask(autoSynchronizeValueTask); autoSynchronizeValueTask.execute(); }//if// }//synchronized// }//else// }//if// }//internalViewSynchronize()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.AbstractComponent#internalResourceHolderChanged(com.foundation.tcv.client.view.ResourceHolder, java.lang.Object, java.lang.Object, int) */ protected void internalResourceHolderChanged(ResourceHolder resource, Object oldValue, Object newValue, int flags) { if(resource == shadowTextHolder) { if(isShowingShadowText) { getSwtText().setText((String) newValue); controlUpdateForegroundColor(); }//if// }//if// else if(resource == shadowTextColorHolder) { setShadowTextColor((JefColor) newValue); }//else if// else if(!getFormat().internalResourceHolderChanged(resource, oldValue, newValue, flags)) { super.internalResourceHolderChanged(resource, oldValue, newValue, flags); }//else if// }//internalResourceHolderChanged()// /** * Sets the text in the text control. * @param newValue The new value's text. */ public void internalSetText(String newValue) { //Only update the text field if the value has changed.// if(!Comparator.equals(newValue, getSwtText().getText())) { isRefreshingText = true; try { String text; //Use the shadow text if the value is null and we are not the focus control.// if((newValue != null) || (getSwtText().isFocusControl())) { text = newValue; isShowingShadowText = false; getFormat().updateShadowTextState(); }//if// else { text = (String) shadowTextHolder.getValue(); isShowingShadowText = true; getFormat().updateShadowTextState(); }//else// //Request that the control update it's foreground color to reflect the latest changes.// controlUpdateForegroundColor(); synchronized(this) { if(autoSynchronizeValueTask != null) { removeTask(autoSynchronizeValueTask); autoSynchronizeValueTask = null; }//if// }//synchronized// serverValue = newValue; getSwtText().setText(text == null ? "" : text); // getSwtText().pack(true); // getSwtText().getShell().layout(true, true); }//try// finally { isRefreshingText = false; }//finally// }//if// }//internalSetText()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.AbstractComponent#internalProcessMessage(com.foundation.tcv.model.ViewMessage) */ public Object internalProcessMessage(ViewMessage viewMessage) { Object result = null; switch(viewMessage.getMessageNumber()) { case MESSAGE_INITIALIZE: { if(getSwtControl() == null) { int style = viewMessage.getMessageSecondaryInteger(); //Link to the parent container.// setContainer((Container) getComponent(viewMessage.getMessageInteger())); getContainer().addComponent(this); //Create the SWT widget.// setSwtWidget(new org.eclipse.swt.widgets.Text(getContainer().getSwtParent(), style)); getSwtWidget().setData(this); if((style & STYLE_READ_ONLY) > 0) { isReadOnly = true; }//if// //If the container has already been initialized then force the parent to re-layout so that this component will appear.// if(getContainer().isInitialized()) { getContainer().getSwtComposite().layout(true, true); }//if// }//if// break; }//case// case MESSAGE_SET_AUTO_SYNCHRONIZE_VALUE: { autoSynchronizeValue = ((Boolean) viewMessage.getMessageData()).booleanValue(); break; }//case// case MESSAGE_SET_AUTO_SYNCHRONIZE_VALUE_DELAY: { autoSynchronizeValueDelay = ((Long) viewMessage.getMessageData()).longValue(); break; }//case// case MESSAGE_SET_SHADOW_TEXT: { shadowTextHolder.setValue(viewMessage.getMessageData()); break; }//case// case MESSAGE_SET_SHADOW_TEXT_COLOR: { shadowTextColorHolder.setValue(viewMessage.getMessageData()); break; }//case// case MESSAGE_SET_SELECT_ON_FOCUS: { setSelectOnFocus(((Boolean) viewMessage.getMessageData()).booleanValue()); break; }//case// case MESSAGE_INITIALIZE_FORMAT: { initializeFormat(viewMessage.getMessageInteger()); break; }//case// case MESSAGE_FORMAT_MESSAGE: { result = getFormat().internalProcessMessage(new ViewMessage(viewMessage.getControlNumber(), viewMessage.getMessageSecondaryInteger(), viewMessage.getMessageData(), viewMessage.getMessageSecondaryData(), viewMessage.getMessageInteger(), -1, viewMessage.isTwoWayMessage())); break; }//case// default: { result = super.internalProcessMessage(viewMessage); }//default// }//switch// return result; }//internalProcessMessage()// /** * Creates a new format instance used by the field as the formatter for text to model conversions. * @return The text format. */ public Format initializeFormat(int formatIdentifier) { switch(formatIdentifier) { case FORMAT_INTEGER: { format = new IntegerFormat(); break; }//case// case FORMAT_FLOAT: { format = new FloatFormat(); break; }//case// case FORMAT_TEXT: { format = new TextFormat(); break; }//case// case FORMAT_CURRENCY: { format = new CurrencyFormat(); break; }//case// case FORMAT_PERCENT: { format = new PercentFormat(); break; }//case// }//switch// return format; }//initializeFormat()// /** * Calculates a default shadow text color. * @return The default shadow text color. */ protected Color calculateDefaultShadowTextSwtColor() { Color foreground = getSwtControl().getBackground(); int red = foreground.getRed(); int green = foreground.getGreen(); int blue = foreground.getBlue(); int total = red + green + blue; int offset = 200; if(total == 0) { red += 67; green += 67; blue += 67; }//if// else if(total > 382) { float redPercent = 1 - (red / total); float greenPercent = 1 - (green / total); float bluePercent = 1 - (blue / total); red += (int) Math.ceil(redPercent * offset); green += (int) Math.ceil(greenPercent * offset); blue += (int) Math.ceil(bluePercent * offset); }//else if// else { float redPercent = red / total; float greenPercent = green / total; float bluePercent = blue / total; red += (int) Math.ceil(redPercent * offset); green += (int) Math.ceil(greenPercent * offset); blue += (int) Math.ceil(bluePercent * offset); }//else// return new Color(getSwtControl().getDisplay(), red, green, blue); }//calculateDefaultShadowTextSwtColor()// /* (non-Javadoc) * @see com.foundation.view.swt.Component#controlUpdateBackgroundColor() */ protected void controlUpdateBackgroundColor() { super.controlUpdateBackgroundColor(); /* TODO: Fix this some day.. //Recalculate the default shadow text color.// //Note: We do this here because the default shadow text color is based on the background color.// //TODO: This should detect if we are using shadow text at all... if(shadowTextColorHolder == null) { if(shadowTextForegroundSwtColor != null) { shadowTextForegroundSwtColor.dispose(); shadowTextForegroundSwtColor = null; }//if// shadowTextForegroundSwtColor = calculateDefaultShadowTextSwtColor(); }//if// */ }//controlUpdateBackgroundColor()// /* (non-Javadoc) * @see com.foundation.view.swt.Component#controlUpdateForegroundColor() */ protected void controlUpdateForegroundColor() { if(isShowingShadowText) { if(shadowTextForegroundSwtColor != null) { getSwtControl().setForeground(shadowTextForegroundSwtColor); }//if// else { //super.controlUpdateForegroundColor(); getSwtControl().setForeground(getSwtText().getDisplay().getSystemColor(SWT.COLOR_GRAY)); }//else// }//if// else if(getCurrentFormatForegroundColor() != null) { getSwtControl().setForeground(getCurrentFormatForegroundColor()); }//else if// else { super.controlUpdateForegroundColor(); }//else// }//controlUpdateForegroundColor()// /** * Gets the current format foreground color used by the component. * @return The format's currently used foreground color, or null if none is provided. */ protected Color getCurrentFormatForegroundColor() { return currentFormatForegroundColor; }//getCurrentFormatForegroundColor()// /** * Sets the current format foreground color used by the component. * @param currentFormatForegroundColor The format's currently used foreground color, or null if none is provided. */ protected void setCurrentFormatForegroundColor(Color currentFormatForegroundColor) { this.currentFormatForegroundColor = currentFormatForegroundColor; }//setCurrentFormatForegroundColor()// /** * Called by the Format to alter the foreground color, overriding temporarily the foreground color set by the underlying control, but not the shadow text. * @param color The foreground color to override the default component foregound color, or null to undo the override. */ protected void internalSetFormatForegroundColor(JefColor color) { //Since this method is called often with the same parameter, stop the call here if we aren't changing anything.// if(!Comparator.equals(color, currentFormatForegroundJefColor)) { currentFormatForegroundJefColor = color; if(getCurrentFormatForegroundColor() != null) { destroyColor(getCurrentFormatForegroundColor()); }//if// setCurrentFormatForegroundColor(createColor(color)); controlUpdateForegroundColor(); }//if// }//internalSetFormatForegroundColor()// /* (non-Javadoc) * @see org.eclipse.swt.events.FocusListener#focusGained(org.eclipse.swt.events.FocusEvent) */ public void focusGained(FocusEvent event) { if(isShowingShadowText) { isShowingShadowText = false; getFormat().updateShadowTextState(); getSwtText().setText(""); controlUpdateForegroundColor(); }//if// if(selectOnFocus) { //Allow the focus selection to be disabled if we are re-activating the shell.// if(!disableFocusSelection) { //Call selectAll within a runnable so that it occurs after the mouse down event (which is called if focus was gained due to a mouse click and which will clear the selection).// getDisplay().asyncExec(new Runnable() { public void run() { Text text = getSwtText(); if(text != null && !text.isDisposed()) { //text.selectAll(); text.setSelection(text.getCharCount(), 0); }//if// }//run()// }); }//if// }//if// }//focusGained()// /* (non-Javadoc) * @see org.eclipse.swt.events.FocusListener#focusLost(org.eclipse.swt.events.FocusEvent) */ public void focusLost(FocusEvent event) { getFormat().focusLost(event); if(shadowTextHolder.getValue() != null) { String controlText = getSwtText().getText(); if((controlText == null) || (controlText.length() == 0)) { isShowingShadowText = true; getFormat().updateShadowTextState(); getSwtText().setText(shadowTextHolder.getValue() != null ? (String) shadowTextHolder.getValue() : ""); controlUpdateForegroundColor(); }//if// }//if// synchronized(this) { if(autoSynchronizeValueTask != null) { removeTask(autoSynchronizeValueTask); autoSynchronizeValueTask.execute(); }//if// }//synchronized// }//focusLost()// /* (non-Javadoc) * @see org.eclipse.swt.events.VerifyListener#verifyText(org.eclipse.swt.events.VerifyEvent) */ public void verifyText(VerifyEvent event) { if(!isRefreshingText) { getFormat().verifyText(event); }//if// }//verifyText()// /* (non-Javadoc) * @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent) */ public void modifyText(ModifyEvent event) { if((autoSynchronizeValue) && (!isRefreshingText) && (!isReadOnly)) { final Object value = getFormat().getModelValue(); if(!Comparator.equals(value, serverValue)) { if(autoSynchronizeValueDelay > 0) { //Start a task to send the text to the server after a short delay. This will reduce the overhead of auto synchronizing a text field.// synchronized(this) { if(autoSynchronizeValueTask != null) { removeTask(autoSynchronizeValueTask); }//if// autoSynchronizeValueTask = new Task() { boolean hasRun = false; public void execute() { //Make sure that this task is still valid and is not being superceeded.// synchronized(TextField.this) { if((autoSynchronizeValueTask == this) && !hasRun) { hasRun = true; //Cleanup after the task.// autoSynchronizeValueTask = null; getEventLoop().executeAsync(new IRunnable() { public Object run() { //Clear the decoration.// removeDecoration(getFormat().originalValueChangedDecoration); getFormat().originalValueChangedDecoration = null; //Store the new server model value.// getFormat().setServerModelValue(value); //Update the server.// getFormat().setModelValue(value); return null; }//run()// }); }//if// }//synchronized// }//run()// }; addTask(autoSynchronizeValueTask, autoSynchronizeValueDelay); }//synchronized// }//if// else { getFormat().setModelValue(value); }//else// }//if// }//if// }//modifyText()// /* (non-Javadoc) * @see org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.KeyEvent) */ public void keyPressed(KeyEvent e) { super.keyPressed(e); //Note: The keyCode will be zero if a special key was not pressed. If the keyCode is zero then character is the character pressed starting at 'a' = 1.// if((e.keyCode == 'a') && (e.stateMask == SWT.CTRL)) { //Work around the missing ctrl-a short cut for selecting all the text.// getSwtText().selectAll(); e.doit = false; }//if// }//keyPressed()// /* (non-Javadoc) * @see org.eclipse.swt.events.KeyListener#keyReleased(org.eclipse.swt.events.KeyEvent) */ public void keyReleased(KeyEvent e) { //Do nothing.// }//keyReleased()// /* (non-Javadoc) * @see org.eclipse.swt.events.ShellListener#shellIconified(org.eclipse.swt.events.ShellEvent) */ public void shellIconified(ShellEvent e) { }//shellIconified()// /* (non-Javadoc) * @see org.eclipse.swt.events.ShellListener#shellDeiconified(org.eclipse.swt.events.ShellEvent) */ public void shellDeiconified(ShellEvent e) { }//shellDeiconified()// /* (non-Javadoc) * @see org.eclipse.swt.events.ShellListener#shellDeactivated(org.eclipse.swt.events.ShellEvent) */ public void shellDeactivated(ShellEvent e) { //Set the flag indicating the shell was deactivated and the focus should not be reset upon activation.// disableFocusSelection = true; }//shellDeactivated()// /* (non-Javadoc) * @see org.eclipse.swt.events.ShellListener#shellActivated(org.eclipse.swt.events.ShellEvent) */ public void shellActivated(ShellEvent e) { //Clear the flag in a runnable so it occurs after the focusGained event is called - thus preventing focusGained from changing the selection.// getDisplay().asyncExec(new Runnable() { public void run() { disableFocusSelection = false; }//run()// }); }//shellActivated()// /* (non-Javadoc) * @see org.eclipse.swt.events.ShellListener#shellClosed(org.eclipse.swt.events.ShellEvent) */ public void shellClosed(ShellEvent e) { }//shellClosed()// }//TextField//