/*
Copyright (c) 2003-2008 ITerative Consulting Pty Ltd. All Rights Reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
o Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
o Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the distribution.
o This jcTOOL Helper Class software, whether in binary or source form may not be used within,
or to derive, any other product without the specific prior written permission of the copyright holder
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package DisplayProject;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.CaretEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.Document;
import javax.swing.text.DocumentFilter;
import javax.swing.text.MaskFormatter;
import javax.swing.text.NavigationFilter;
import org.apache.log4j.Logger;
import DisplayProject.events.ChildEventHelper;
import DisplayProject.events.ChildInstallableListener;
import DisplayProject.events.ClientEventManager;
import DisplayProject.events.InstallableListener;
import DisplayProject.factory.DataFieldFactory;
import DisplayProject.table.ArrayFieldCellHelper;
import DisplayProject.table.ArrayTableComponentCorrector;
import Framework.CloneHelper;
import Framework.DataValue;
import Framework.DateTimeNullable;
import Framework.DecimalNullable;
import Framework.DoubleData;
import Framework.ErrorMgr;
import Framework.EventManager;
import Framework.ForteKeyboardFocusManager;
import Framework.FrameworkUtils;
import Framework.ParameterHolder;
import Framework.TextData;
import Framework.TextNullable;
import Framework.UsageException;
/**
* Subclass of {@link javax.swing.JFormattedTextField} that attempts to behave in the same way as
* the corresponding Forte component(s).
*
* To provide a masked and templated date field use:
* new DisplayProject.DataField(new DateFormatter(new SimpleDateFormat("dd/MM/yyyy"), DateTimeData.class));
*
* To provide a masked field use:
* MaskFormatter mf = new MaskFormatter("***-******-********");
* mf.setPlaceholderCharacter('_');
* mf.setValueContainsLiteralCharacters(false);
* new DisplayProject.DataField(mf);
*
* To provide a limited length numeric field:
* new DisplayProject.DataField(new FixedLengthFieldFilter(Constants.MK_INTEGER, 7));
*
*/
@SuppressWarnings("serial")
public class DataField extends JFormattedTextField implements CharacterField, CloneableComponentWithProperties, ArrayTableComponentCorrector {
public static final int LEFT_PAD = 3;
protected transient boolean firstKey = true;
protected transient Pattern regex;
protected transient Matcher matcher;
protected transient boolean validateInput = true;
protected int maskType = Constants.MK_NONE;
protected Object lastValue = null;
@SuppressWarnings("deprecation")
protected transient DataMap map = null;
protected int maxLength = 0;
protected boolean invokedByToData = false;
private DocumentFilter filter;
private boolean nullValueAllowed = false;
protected boolean isValueNull = false;
private SimpleDateFormat dateFmt = null;
private JTable table = null;
private int tableRow = -1;
private int tableColumn = -1;
protected boolean postEvents = true;
protected boolean purging = false;
private String originalFormatText = null;
private static Logger _log = Logger.getLogger(DataField.class);
private DataField editor;
private DataField renderer;
private DataField columnTemplate;
private boolean showCaret = true; // CraigM:19/11/2008.
/**
* If postEvents is false, we may not be allowed to change the focus as well. This field
* flags whether proper focus handling is enabled or not.
*/
protected boolean allowFocusChange = false;
protected boolean ValidateOnKeystroke = false;
// protected NumberFormat NumericFmt = null;
/**
* Caching of the field height for efficiency
*/
private int fieldHeight = 0;
/**
* Caching of the column width for efficiency
*/
private int columnWidth = 0;
/**
* A class used to roll back this last edit when PurgeEvents is called.
*/
private class RollbackAction implements Runnable {
Object oldValue;
public RollbackAction() {
this.oldValue = DataField.this.getValue();
}
public void run() {
// Defer this processing until the EDT is idle
if (Toolkit.getDefaultToolkit().getSystemEventQueue().peekEvent() == null) {
Object tempLastValue = DataField.this.getValue();
DataField.this.setValue(oldValue);
lastValue = tempLastValue;
firstKey = false;
purging = true;
}
else {
SwingUtilities.invokeLater(this);
}
}
}
/**
* This method marks the current data field as "dirty". That is, it pretends that the
* user has already changed the contents of the field, irrespective of whether they
* have or not.<p>
* <p>
* This method should not be called as part of normal processing. It can be called if
* explicity setting the focus in a data field and wanting the validation to re-occur
* when focus leaves that data field.
*/
public void markFieldAsDirty() {
firstKey = false;
purging = false;
}
/**
* @return Returns the filter.
*/
public DocumentFilter getFilter() {
return this.filter;
}
/**
* @param theFilter The filter to set.
*/
public void setFilter(DocumentFilter theFilter) {
this.filter = theFilter;
}
public DataField() {
super();
this.setupWidget();
this.setupDocumentFormatter();
}
public DataField(Object value) {
super(value);
this.setupWidget();
}
public DataField(Document doc) {
super();
this.setDocument(doc);
this.setupDocumentFormatter();
this.setupWidget();
}
public DataField(DocumentFilter filter) {
super();
this.setupDocumentFormatter();
((DataFieldFormatter)this.getFormatter()).setFilter(filter);
((DataFieldFormatter)this.getFormatter()).install(this);
this.setupWidget();
}
public DataField(Format format) {
super(format);
this.setupWidget();
}
public DataField(AbstractFormatter formatter) {
// TF:17/4/08:Changed this method so we could override the formatter if needed
super();
if (formatter.getClass().equals(MaskFormatter.class)) {
MaskFormatter mf = (MaskFormatter)formatter;
try {
NonMandatoryMaskFormatter newFormatter = new NonMandatoryMaskFormatter(mf.getMask());
newFormatter.setPlaceholderCharacter(mf.getPlaceholderCharacter());
newFormatter.setValueContainsLiteralCharacters(mf.getValueContainsLiteralCharacters());
newFormatter.setAllowsInvalid(mf.getAllowsInvalid());
newFormatter.setValidCharacters(mf.getValidCharacters()); // CraigM:07/01/2009.
formatter = newFormatter;
}
catch (ParseException e) {
// Not much we can do here, shouldn't happen anyway. Just ignore it and leave the original formatter
}
}
this.setFormatterFactory(new DefaultFormatterFactory(formatter));
this.setupWidget();
}
public DataField(AbstractFormatterFactory factory) {
super(factory);
this.setupWidget();
}
public DataField(AbstractFormatterFactory factory, Object currentValue) {
super(factory, currentValue);
this.setupWidget();
}
/**
* Set the widget properties associated with the control
*/
private void setupWidget() {
// TF:24/07/2008:Set the default background colour to inherit
this.setBackground(null);
this.customBorder();
this.setupEnterKeyListener();
this.addMouseListener(StatusTextListener.sharedInstance());
// TF:11/12/2009:DET-141:Allowed this control to inherit its popup menus
this.setInheritsPopupMenu(true);
}
public int getMaxLength() {
return maxLength;
}
public void setMaxLength(int maxLength) {
this.maxLength = maxLength;
}
private void setupDocumentFormatter() {
/* DefaultFormatterFactory factory = new DefaultFormatterFactory();
DataFieldFormatter format = new DataFieldFormatter();
factory.setDefaultFormatter(format);
this.setFormatterFactory(factory);
this.setFormatter(format);
*/ //this.setFocusLostBehavior(JFormattedTextField.PERSIST);
}
/**
* Provide a mechanism to propagate an enter key to the default button
*/
private void setupEnterKeyListener() {
this.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JRootPane aRootPane = getRootPane();
if (aRootPane != null) {
JButton aDefaultButton = aRootPane.getDefaultButton();
if (aDefaultButton!= null && aDefaultButton.isEnabled()) {
loseFocus(Constants.FC_KEYNEXT);
aDefaultButton.requestFocus();
UIutils.invokeOnGuiThread(new Runnable () {
public void run() {
getRootPane().getDefaultButton().doClick();
}
});
}
}
}
});
// Also set up an escape key to override the default one to do nothing.
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "none");
}
boolean thisFieldIsInError = false;
/**
* This method post the correct Forte focus events
*/
protected void processFocusEvent(FocusEvent e) {
_log.debug("FocusEvent: " + e.toString());
//if (e.isTemporary()) return;
if (!postEvents){
postEvents = true;
if (allowFocusChange) {
super.processFocusEvent(e);
allowFocusChange = false;
}
return;
}
// it is possible for the super focus event to call back to ourselves, possibly altering
// the state our our firstKey property. Hence, we temporarily remember this value
boolean oldFirstKey = this.firstKey;
// TF:9/10/07:We want to process the superclass focus events but not actually post the AfterFocusGain/BeforeFocusLoss
// events to the listeners. The reason for this is the data in the data field has not committed at this point in time,
// it gets committed on the loseFocus method call later. We could just begin the event chain (we'll do this anyway)
// so the Event Manager doesn't post the events until the end of the chain, but this will put the events out of order.
// The owning container focus events will fire first, which is wrong. Hence, we remove them and explicitly fire them later.
FocusListener[] listeners = getFocusListeners();
ArrayList<FocusListener> pendingListeners = new ArrayList<FocusListener>();
for (FocusListener l : listeners) {
if (l instanceof ChildInstallableListener || l instanceof InstallableListener) {
removeFocusListener(l);
pendingListeners.add(l);
}
}
try {
EventManager.startEventChain();
super.processFocusEvent(e);
this.firstKey = oldFirstKey;
if (e.getID() == FocusEvent.FOCUS_GAINED) {
// TF:12/06/2008:LIB-16: Added the test in so if we're gaining focus from another
// window we don't process this code, as forte basically ignored focus changes to
// other windows.
if (e.getOppositeComponent() != null && UIutils.getWindowForComponent(e.getOppositeComponent()) == UIutils.getWindowForComponent(this)) {
// If the field is currently in error, and the user tabs or clicks away from it, we'll
// lose the focus, validate, display the error message and then return to the field.
// Unfortunately, this returning to the field generates a FOCUS_GAINED event, and we
// definitely don't want to reset the firstKey flag (otherwise the verification passes
// next time)
if (!thisFieldIsInError && !this.purging) {
this.firstKey = true;
}
else {
// Only purge the first focus gain event
this.resetPurgingFlag();
}
// CraigM:16/09/2008 - Set the caret position to where we clicked
if (ForteKeyboardFocusManager.getTraversalReason() == Constants.FC_MOUSECLICK) {
Point mousePos = this.getMousePosition();
// TF:25/09/2008:Fixed this up to stop it crashing the EDT in some cirumstances, such
// as when transferring focus to the data field from a non-java window on a very wise screen
// (ie something with a very large x-coordinate, eg 2000)
if (mousePos != null) {
int caretPos = this.viewToModel(mousePos);
this.setCaretPosition(caretPos);
}
}
// CraigM:16/09/2008 - If traversing, select all the text
else if (ForteKeyboardFocusManager.getTraversalReason() == Constants.FC_KEYNEXT ||
ForteKeyboardFocusManager.getTraversalReason() == Constants.FC_KEYPREV ||
ForteKeyboardFocusManager.getTraversalReason() == Constants.FC_KEYUP || // CraigM:09/01/2009 - Highlight all when traversing with up/down arrows
ForteKeyboardFocusManager.getTraversalReason() == Constants.FC_KEYDOWN ||
ForteKeyboardFocusManager.getTraversalReason() == Constants.FC_APPLICATION) { // CraigM:16/01/2009 - Highlight all when traversing via code
this.selectAllTextOnFocusGain();
}
// TF:12/10/07:If we're an editor of an array field, don't post this as the array field will do it explicitly
if (ArrayFieldCellHelper.getArrayField(this) == null) {
this.postAfterFocusGain();
}
// Post the events to the handlers it wasn't posted to earlier
for (FocusListener l : pendingListeners) {
l.focusGained(e);
}
// CraigM:19/11/2008 - Hide the caret if necessary.
if (showCaret == false) {
this.getCaret().setVisible(false);
}
}
// TF:14/04/2010:If we have no opposite component, and the reason is APPLICATION, we're creating the
// window and having initial entry, so select the field.
else if (e.getOppositeComponent() == null && ForteKeyboardFocusManager.getTraversalReason() == Constants.FC_APPLICATION) {
this.selectAllTextOnFocusGain();
}
}
else if (e.getID() == FocusEvent.FOCUS_LOST) {
// We're definitely in the field, so we can't be purging
this.resetPurgingFlag();
if (e.getOppositeComponent() instanceof JButton) {
JButton cancel = (JButton)e.getOppositeComponent();
if(cancel.getVerifyInputWhenFocusTarget()==false) {
return;
}
}
if (e.getOppositeComponent() instanceof JMenuItem) {
JMenuItem cancel = (JMenuItem)e.getOppositeComponent();
if(cancel.getVerifyInputWhenFocusTarget()==false) {
return;
}
}
/*
* determine if we are traversing forward or backward and provide the correct focus loss reason
*/
// TF:If we're an editor of an array field, don't post this as the array field will do it explicitly
if (ArrayFieldCellHelper.getArrayField(this) == null) {
this.loseFocus(this.getFocusLossReason(e));
}
// TF:12/10/07:We shouldnt need this any more
//Let parent know about focus loss
// if (getParent() != null && getParent() instanceof JTable)
// ((ArrayListModel)((JTable)getParent()).getModel()).focusLost(e);
// Post the events to the handlers it wasn't posted to earlier
for (FocusListener l : pendingListeners) {
l.focusLost(e);
}
}
}
finally {
EventManager.endEventChain();
// We must add out focus listeners back in
for (FocusListener l : pendingListeners) {
addFocusListener(l);
}
}
}
/**
* This code is very similar to JTextComponent.selectAll, but SelectAll leaves the dot at the
* right hand edge of the selected field, whereas forte would normally leave in on the left hand
* edge. The Java one is more consistent with what users expect, but the code is commonised into
* a routine here so that it reproduces the Forte functionality faithfully. If desired, the simpler
* selectAll() code can be used instead.
*/
private void selectAllTextOnFocusGain() {
// this.selectAll();
Document doc = getDocument();
if (doc != null) {
setCaretPosition(doc.getLength());
moveCaretPosition(0);
}
}
public int getFocusLossReason(FocusEvent e) {
return ForteKeyboardFocusManager.getTraversalReason();
}
public void paste() {
/* PM:12/10/07
* Forte would allow the past of text into a field even
* if the text in the clipboard was longer than the allowed.
* It would simply truncate it
* See @DisplayProject.FixedLengthDocument
*/
// CONV:TF:21 Jul 2009:Fixed this to work properly
if (isEditable() && isEnabled()) {
int finalCursorPosition = 0;
boolean didManualPaste = false;
if (getDocument() instanceof FixedLengthDocument){
FixedLengthDocument doc = (FixedLengthDocument)getDocument();
Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
try {
if (doc.getMaxLength() > 0 && t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
String text = (String)t.getTransferData(DataFlavor.stringFlavor);
// CONV:TF:21 Jul 2009:Insert the text at the correct spot in the current String. The String is truncated
// to the maximum number of characters, and the cursor is moved to the end of the selection or the
// end of string, whichever is smaller.
if (text.length()+doc.getLength() > doc.getMaxLength()) {
String currentText = doc.getText(0, doc.getLength());
int caretMark = this.getCaret().getMark();
int caretDot = this.getCaret().getDot();
String newText = currentText.substring(0, Math.min(caretMark, caretDot)) + text + currentText.substring(Math.max(caretMark, caretDot));
this.setText(newText);
finalCursorPosition = Math.min(caretDot+text.length(), doc.getMaxLength());
didManualPaste = true;
}
}
} catch (UnsupportedFlavorException e) {
} catch (IOException e) {
} catch (BadLocationException e) {
}
}
if (!didManualPaste) {
super.paste();
finalCursorPosition = this.getCaretPosition();
}
KeyEvent event = new KeyEvent(this, 0, 0, KeyEvent.VK_CONTROL, KeyEvent.VK_V, 'V');
keyStroke(event);
// CONV:TF:21 Jul 2009:We need to reset the cursor to the appropriate place, in case
// a menu pick will give focus back to this control, thus resetting the location to the start of the field
final int position = finalCursorPosition;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setCaretPosition(position);
}
});
}
}
private void resetPurgingFlag() {
if (this.purging) {
this.purging = false;
this.firstKey = false;
}
}
protected void processKeyEvent(KeyEvent e) {
int code = e.getKeyCode();
char ch = e.getKeyChar();
int modifyers = e.getModifiers();
// Focus must be in the field to get the keystroke, so we can't be purging
this.resetPurgingFlag();
/*
* if the key code is a action key
* let the super process it
*/
if(code != KeyEvent.VK_UNDEFINED){
if (e.getID() == KeyEvent.KEY_PRESSED){
if (code == KeyEvent.VK_BACK_SPACE && this.isEditable()){
int pos = getCaretPosition();
int ss = getSelectionStart();
int ee = getSelectionEnd();
if (pos == 0 && ee == 0) return;
try {
if (ss != ee) {
getDocument().remove(ss, ee-ss);
} else {
getDocument().remove(pos-1, 1);
}
keyStroke(e);
} catch (BadLocationException e1) {
_log.error("BadLocation exception in DataField:Backspace", e1);
}
} else if (code == KeyEvent.VK_DELETE && this.isEditable()) {
int pos = getCaretPosition();
int ss = getSelectionStart();
int ee = getSelectionEnd();
int lastPosition = getDocument() == null? -1: getDocument().getEndPosition().getOffset() - 1;
if (pos == lastPosition && ee == lastPosition && ss == lastPosition) return;
try {
if (ss != ee) {
getDocument().remove(ss, ee-ss);
} else {
getDocument().remove(pos, 1);
}
keyStroke(e);
} catch (BadLocationException e1) {
_log.error("BadLocation exception in DataField:Delete", e1);
}
//PM:27/11/07 special case for CUT to ensure after value change occurs
} else if (code == KeyEvent.VK_X && modifyers == KeyEvent.CTRL_MASK && this.isEditable()) {
super.processKeyEvent(e);
keyStroke(e);
//PM:26/11/07 specific case to handle PASTE correctly
} else if (code == KeyEvent.VK_V && modifyers == KeyEvent.CTRL_MASK && this.isEditable()) {
// TF:21/07/2009:Changed this to use the standard paste.
this.paste();
// try {
// Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
// String pasteString = (String)cb.getData(DataFlavor.stringFlavor);
// if (pasteString != null){
// this.replaceSelection(pasteString);
// keyStroke(e);
// }
// } catch (UnsupportedFlavorException e1) {
// _log.error("UnsupportedFlavorException exception in DataField:Paste", e1);
// } catch (IOException e2) {
// _log.error("IOException exception in DataField:Paste", e2);
// }
} else {
super.processKeyEvent(e);
}
}
return;
}
/*
* otherwise process it as a character
*/
else {
if (Character.isIdentifierIgnorable(ch)) return;
switch (this.maskType){
case Constants.MK_ALPHA_LOWER:
case Constants.MK_LOWERCASE:
case Constants.MK_ALPHANUM_LOWER:
ch = Character.toLowerCase(ch);
e.setKeyChar(ch);
break;
case Constants.MK_ALPHA_UPPER:
case Constants.MK_ALPHANUM_UPPER:
case Constants.MK_UPPERCASE:
ch = Character.toUpperCase(ch);
e.setKeyChar(ch);
break;
default:
}
boolean accepted = acceptChar(ch) && this.isEditable() &&
!e.isAltDown() && !e.isActionKey() &&
!e.isAltGraphDown() && !e.isMetaDown() &&
!e.isControlDown();
if(accepted){
//_log.debug(".. accepted");
super.processKeyEvent(e);
//_log.debug("... processed by super");
keyStroke(e);
} else {
java.awt.Toolkit.getDefaultToolkit().beep();
//_log.debug(".. rejected");
e.consume();
}
e.consume();
}
}
/**
* Fire the property change, but ensure that trailing spaces are stripped off, as Forte does this too.
*/
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
if (newValue instanceof String) {
super.firePropertyChange(propertyName, oldValue, FrameworkUtils.trimTrailing((String)newValue) );
}
// TF:18/09/2009:If there is, for example, a data field mapped to a TextData and the field does not have validate on
// keystroke on and is using a fixed length document (or any other document which will call replace on the keystrokes
// and hence try to commit to the underlying data model) we run into a situation where the old value is the same reference
// as the new value, so the firing property change will have no effect. Thus we force the old value to null, otherwise
// reading the value back after the user has edited it (for example in the AfterValueChange event) will show the old code.
// It is possible we could also get around this be setting the "setCommitsOnValidEdit()" flag on the DataFieldFormatter to
// false (hence eliminating undue commits) but there is much greater risk of regression of doing this at this point.
else if (newValue == oldValue && newValue instanceof DataValue && this.getDocument() instanceof FixedLengthDocument) {
super.firePropertyChange(propertyName, null, newValue);
} else {
super.firePropertyChange(propertyName, oldValue, newValue);
}
}
/**
* CraigM:16/09/2008.
* @param e
*/
private void postAfterKeyPress(KeyEvent e) {
// Create parameters
Hashtable<String, Object> qq_Params = new Hashtable<String, Object>();
// add keypress parameters
qq_Params.put("key", new ParameterHolder(e.getKeyCode()));
qq_Params.put("value", new ParameterHolder(e.getKeyChar()));
qq_Params.put("modifier", new ParameterHolder(e.getModifiers()));
// add usual parameters
final Hashtable<String, ParameterHolder> usualParameters = arrayParam();
if (usualParameters != null) {
qq_Params.putAll(usualParameters);
}
ClientEventManager.postEvent(correctPostingComponent(), "AfterKeyPress", qq_Params);
}
protected void keyStroke(KeyEvent e) {
// Since we've definitely got the focus back in the window, we can't be purging
this.resetPurgingFlag();
if (this.ValidateOnKeystroke) {
this.postAfterValueChange(false);
}
this.postAfterKeyPress(e);
if (this.firstKey) {
this.firstKey = false;
this.invokedByToData = true;
if (postEvents && !ValidateOnKeystroke){
ClientEventManager.postEvent( correctPostingComponent(), "AfterFirstKeystroke", arrayParam() );
// TF:27/9/07:Revamped to use new event poster
ChildEventHelper.postEventToAllParents(correctPostingComponent(), "ChildAfterFirstKeystroke");
}
this.invokedByToData = false;
}
}
/**
* Determine whether or not this character should be accepted by this datafield. Uses regular expressions.
* @param ch
* @return
*/
private boolean acceptChar(char ch){
if (regex == null) return true;
String s = "";
try {
// Form the string as it will be once this character is accepted, and
// then see if this matches our regular expression
s = getDocument().getText(0, getDocument().getLength());
int selStart = this.getSelectionStart();
int selEnd = this.getSelectionEnd();
s = s.substring(0, selStart) + ch + s.substring(selEnd);
}
catch (BadLocationException e) {
}
Matcher matcher = regex.matcher(s);
return matcher.matches();
// String s = Character.toString(ch);
// matcher = regex.matcher(s);
// return matcher.matches();
}
public void postAfterFocusGain() {
int reason = ForteKeyboardFocusManager.getTraversalReason();
if (reason != Constants.FC_SUPRESS) {
Hashtable<String, ParameterHolder> params = arrayParam();
if (params == null) {
params = new Hashtable<String, ParameterHolder>();
}
params.put("reason", new ParameterHolder(reason));
ClientEventManager.postEvent( correctPostingComponent(), "AfterFocusGain", params );
}
}
public void postBeforeFocusLoss(int reason) {
EventManager.startEventChain();
FocusHelper.addSetFocusPurgeAction(this);
// FocusHelper.addPurgeAction(new RollbackAction());
Hashtable<String, ParameterHolder> params = arrayParam() ;
if (params == null)
params = new Hashtable<String, ParameterHolder>();
int realReason = ForteKeyboardFocusManager.getTraversalReason();
if (realReason != Constants.FC_SUPRESS) {
params.put("reason", new ParameterHolder(realReason));
ClientEventManager.postEvent( correctPostingComponent(), "BeforeFocusLoss", params);
}
EventManager.endEventChain();
}
public boolean postAfterValueChange() {
return this.postAfterValueChange(true);
}
public boolean postAfterValueChange(boolean pIsFromFocusLoss, boolean pForceValueChangeEvents) {
try {
EventManager.startEventChain();
FocusHelper.addSetFocusPurgeAction(this);
FocusHelper.addPurgeAction(new RollbackAction(), this, "AfterValueChange");
if (!firstKey || ValidateOnKeystroke) {
this.commitEdit();
this.toData(this.getValue());
if (postEvents){
fireAfterValueChangeEvents();
}
lastValue = this.getValue();
if (pIsFromFocusLoss) {
firstKey = true;
}
}
else if (pForceValueChangeEvents) {
this.commitEdit();
this.toData(this.getValue());
fireAfterValueChangeEvents();
}
return true;
}
catch (ParseException pE) {
// _log.error("DataField " + getName() + ".loseFocus() ParseException",pE);
return false;
}
finally {
EventManager.endEventChain();
}
}
public boolean postAfterValueChange(boolean pIsFromFocusLoss) {
return this.postAfterValueChange(pIsFromFocusLoss, false);
}
public void loseFocus(int reason) {
_log.debug("DataField " + getName() + ".loseFocus()");
if (firstKey){
postBeforeFocusLoss(reason);
return;
}
else {
if (!ValidateOnKeystroke) {
postAfterValueChange();
}
if (postEvents){
postBeforeFocusLoss(reason);
}
}
}
protected void fireAfterValueChangeEvents(){
ClientEventManager.postEvent( correctPostingComponent(), "AfterValueChange", arrayParam() );
UIutils.setDataChangedFlag(this);
// TF:Revamped this logic as our renderers are now in panels...
// TF:04/11/2008:Changed this to use the posting component
ChildEventHelper.postEventToAllParents(this.correctPostingComponent(), "ChildAfterValueChange");
}
@SuppressWarnings("deprecation")
public DataMap getMap() {
return map;
}
@SuppressWarnings("deprecation")
public void setMap(DataMap map) {
this.map = map;
}
public void setValue(Object theValue) {
// TF:23/3/08: We cannot affect the passed value by this method. For example, if we're passed a TextData, we cannot
// do: super.setValue(((TextData)theValue).toLower()). The problem with doing this is, if the data field is a column
// template in an array, doing the toLower will fire a property change event which will cause the table data to refresh
// which will cause the selection to change which will cause the cell to be edited again, which will cause the setValue
// method to be called, and so on ad infinitum. This seems to be the way Forte worked anyway.
//if (this.invokedByToData) return;
this.firstKey = true;
if (_log.isDebugEnabled()) {
if (theValue == null)
_log.debug("DataField " + getName() + ".setValue() >null<");
else
_log.debug("DataField " + getName() + ".setValue() >" + theValue.toString() + "<");
}
lastValue = this.getValue();
if (theValue != null && (theValue instanceof TextData || theValue instanceof String)){
switch (this.maskType){
case Constants.MK_ALPHA_LOWER:
case Constants.MK_LOWERCASE:
case Constants.MK_ALPHANUM_LOWER:
// if ( theValue instanceof TextData)
// super.setValue(((TextData)theValue).toLower());
// else
// super.setValue(((String)theValue).toLowerCase());
super.setValue(theValue.toString().toLowerCase());
break;
case Constants.MK_ALPHA_UPPER:
case Constants.MK_ALPHANUM_UPPER:
case Constants.MK_UPPERCASE:
// if ( theValue instanceof TextData)
// super.setValue(((TextData)theValue).toString().toUpperCase());
// else
// super.setValue(((String)theValue).toUpperCase());
super.setValue(theValue.toString().toUpperCase());
break;
default:
super.setValue(theValue);
}
}
else {
try {
// TF:31/10/2008:If there's a number displayed on the screen mapped to a float or a double, and there's
// no underlying mask, we need to limit the precision of the number. For example, 1.23456789 is displayed
// as 1.23457, and can actually change the underlying float. We cannot use formatters to do this for us,
// as the user can validly enter a new value with more digits, like 1.2353544545435 and the data field
// will keep this value. Hence, we must do it here, where the value is loaded.
if ((theValue instanceof Double || theValue instanceof Float) && this.originalFormatText == null) {
super.setValue(Double.valueOf(DoubleData.toString(((Number)theValue).doubleValue())));
}
else {
super.setValue(theValue);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
if (theValue != null) {
this.lastValue = theValue.toString();
}
}
// private void setCorrectValue(DataValue val){
// switch (val.dataType()){
// case Framework.Constants.DV_DT_DECIMAL:
// super.setValue(((DecimalData)val).asDouble());
// break;
// case Framework.Constants.DV_DT_DECIMAL_NULLABLE:
// super.setValue(((DecimalNullable)val).asDouble());
// break;
// case Framework.Constants.DV_DT_INTEGER:
// super.setValue(((IntegerData)val).asInteger());
// break;
// case Framework.Constants.DV_DT_INTEGER_NULLABLE:
// super.setValue(((IntegerNullable)val).asInteger());
// break;
// case Framework.Constants.DV_DT_DOUBLE:
// super.setValue(((DoubleData)val).asDouble());
// break;
// case Framework.Constants.DV_DT_DOUBLE_NULLABLE:
// super.setValue(((DoubleNullable)val).asDouble());
// break;
// case Framework.Constants.DV_DT_DATETIME:
// super.setValue(((DateTimeData)val));
// break;
// case Framework.Constants.DV_DT_DATETIME_NULLABLE:
// super.setValue(((DateTimeNullable)val));
// break;
//// case Framework.Constants.DV_DT_TEXT:
//// super.setValue(((TextData)val).asString());
//// break;
//// case Framework.Constants.DV_DT_TEXT_NULLABLE:
//// super.setValue(((TextNullable)val).asString());
//// break;
//// case Framework.Constants.DV_DT_INTERVAL:
//// super.setValue(((IntervalData)val).asString());
//// break;
//// case Framework.Constants.DV_DT_INTERVAL_NULLABLE:
//// super.setValue(((IntervalNullable)val).asString());
//// break;
// default:
// super.setValue(val.toString());
// }
// }
//
public void fromData() {
if (invokedByToData) {
invokedByToData = false;
return;
}
else {
UIutils.invokeOnGuiThread(new Runnable () {
@SuppressWarnings("deprecation")
public void run() {
if (map != null)
map.fromData();
}
});
}
}
public void fromData(boolean forceUpdate) {
invokedByToData = !forceUpdate;
this.fromData();
}
@SuppressWarnings("deprecation")
public void toData(Object o) {
if (_log.isDebugEnabled()) {
_log.debug("DataField " + getName() + ".toData() >" + ((o == null) ? "null" : o.toString()) + "<");
}
if (map != null) {
try {
map.toData(parseString((o == null) ? "" : o.toString()));
} catch (ParseException e) {
UsageException errorVar = new UsageException("DataField " + getName() + ".toData() parse error" , e);
ErrorMgr.addError(errorVar);
throw errorVar;
}
invokedByToData = true;
}
}
/**
* We need to get the mask type in the FormattedCellEditor. CraigM:13/01/2009.
*/
public int getMaskType() {
return this.maskType;
}
public void setMaskType(int maskType) {
this.maskType = maskType;
String pattern = ".*";
switch (this.maskType){
case Constants.MK_ALPHA_LOWER:
pattern = "[a-z]*";
break;
case Constants.MK_ALPHA_MIXED:
pattern = "[A-Za-z]*";
break;
case Constants.MK_ALPHA_UPPER:
pattern = "[A-Za-z]*";
break;
case Constants.MK_ALPHANUM_LOWER:
pattern = "[a-z0-9]*";
break;
case Constants.MK_ALPHANUM_MIXED:
pattern = "[a-zA-z0-9]*";
break;
case Constants.MK_ALPHANUM_UPPER:
pattern = "[A-Z0-9]*";
break;
case Constants.MK_DEFAULT:
case Constants.MK_NONE:
pattern = ".*";
break;
case Constants.MK_FLOAT:
pattern = "[+-]?\\d*\\.?\\d*";
setHorizontalAlignment(javax.swing.JTextField.RIGHT);
//setFormatter(new NumberFormatter());
break;
case Constants.MK_INTEGER:
pattern = "[+-]?\\d*"; //PM:02/10/2008:removed cardinality
setHorizontalAlignment(javax.swing.JTextField.RIGHT);
//setFormatter(new NumberFormatter());
break;
case Constants.MK_LOWERCASE:
pattern = ".*";
break;
case Constants.MK_NUMERIC:
pattern = "\\d*";
//pattern = "[-+]?([0-9]*\\.)?[0-9]+([eE][-+]?[0-9]+)?";
//pattern = "[0-9\\.e]*";
setHorizontalAlignment(javax.swing.JTextField.RIGHT);
//setFormatter(new NumberFormatter());
break;
case Constants.MK_TEMPLATE:
pattern = ".*";
break;
case Constants.MK_UPPERCASE:
pattern = ".*";
break;
}
regex = Pattern.compile(pattern, Pattern.DOTALL);
}
public TextData getTextValue(){
return new TextData((getValue() == null) ? "" : getValue().toString());
}
public void setTextValue(TextData value) {
setValue(value/*.toString()*/);
}
/**
* returns true if null values are allowed
* @return
*/
public boolean isNullValueAllowed() {
if (_log.isDebugEnabled()) {
_log.debug("DataField " + getName() + ".isNullValueAllowed() -->" + Boolean.toString(nullValueAllowed) + "<--");
}
return nullValueAllowed;
}
public void setNullValueAllowed(boolean pNullValueAllowed) {
nullValueAllowed = pNullValueAllowed;
//Ensure NullableMaskFormatter is set correctly
if (getFormatter() instanceof NullableMaskFormatter) {
if (nullValueAllowed != ((NullableMaskFormatter)getFormatter()).canAllowNullable()) {
((NullableMaskFormatter)getFormatter()).setAllowNullable(nullValueAllowed);
}
}
}
public boolean isValueNull() {
String uncommitted = getText();
boolean itSureIs = false;
if ((getFormatter() != null) && (getFormatter() instanceof MaskFormatter)){
try {
MaskFormatter mf = (MaskFormatter)getFormatter();
String noValue = mf.valueToString("");
itSureIs = uncommitted.equals(noValue);
} catch (ParseException e) {
itSureIs = false;
}
} else {
itSureIs = uncommitted.equalsIgnoreCase(DataValue.NL_FMSG_NULLVALUE);
}
if (_log.isDebugEnabled()) {
_log.debug("DataField " + getName() + ".isValueNull() -->" + uncommitted + "<-->" + Boolean.toString(itSureIs) + "<--");
}
return itSureIs;
}
protected void setValidateInput(boolean pValue) {
this.validateInput = pValue;
}
public boolean shouldValidateInput() {
return this.validateInput;
}
protected Object parseString(String value)throws ParseException {
AbstractFormatter af = getEditFormatter();
if (af != null)
return af.stringToValue(value);
else
return value;
}
public AbstractFormatter getEditFormatter(){
DefaultFormatterFactory dff = (DefaultFormatterFactory)getFormatterFactory();
if (dff != null)
return dff.getEditFormatter();
else if (getFormatter() != null)
return getFormatter();
else
return null;
}
public SimpleDateFormat getDateFmt() {
return this.dateFmt;
}
public void setDateFmt(SimpleDateFormat dateFmt) {
this.dateFmt = dateFmt;
//NumericFmt = null;
}
public void setDateTemplate(TextData template) {
this.dateFmt = new SimpleDateFormat(template.toString());
((DataFieldVerifier)this.getInputVerifier()).setDateFormat(this.dateFmt);
}
//
// public NumberFormat getNumericFmt() {
// return NumericFmt;
// }
//
// public void setNumericFmt(NumberFormat numericFmt) {
// NumericFmt = numericFmt;
// DateFmt = null;
// }
protected Hashtable<String, ParameterHolder> arrayParam(){
Hashtable<String, ParameterHolder> qq_Params = null;
if (table != null){
qq_Params = new Hashtable<String, ParameterHolder>();
// TF:27/9/07:Made the row and column 1-based indexes instead of 0-based
qq_Params.put( "row", new ParameterHolder(tableRow+1) );
qq_Params.put( "column", new ParameterHolder(tableColumn+1) );
qq_Params.put( "ArrayField", new ParameterHolder(table) );
}
return qq_Params;
}
public JTable getTable() {
return this.table;
}
public void setTable(JTable table) {
this.table = table;
}
public int getTableColumn() {
return this.tableColumn;
}
public void setTableColumn(int tableColumn) {
this.tableColumn = tableColumn;
}
public int getTableRow() {
return this.tableRow;
}
public void setTableRow(int tableRow) {
this.tableRow = tableRow;
}
public Object getLastValue() {
return lastValue;
}
public void setLastValue(Object lastValue) {
this.lastValue = lastValue;
}
public void disableEvents(){
this.postEvents = false;
}
public void disableEvents(boolean pAllowFocusChange) {
this.disableEvents();
allowFocusChange = pAllowFocusChange;
}
public String toString() {
if (this.columnTemplate != null){
return "CellEditor clone of DataField["+getName()+","+getText()+ ", " + "height " + getHeight()+ ", " + " width " + getWidth() + "]";
} else {
return "DataField["+getName()+","+getText()+ ", " + "height " + getHeight()+ ", " + " width " + getWidth() + "]";
}
}
public boolean isValidateOnKeystroke() {
return ValidateOnKeystroke;
}
public void setValidateOnKeystroke(boolean validateOnKeystroke) {
ValidateOnKeystroke = validateOnKeystroke;
}
public String getOriginalFormatText() {
return originalFormatText;
}
/**
* Show or hide the caret. CraigM:19/11/2008.
*/
public void setShowCursor(final boolean show) {
if (this.hasFocus()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
DataField.this.getCaret().setVisible(show);
}
});
}
this.showCaret = show;
}
/**
* @return if the caret should be shown or not. CraigM:19/11/2008.
*/
public boolean getShowCursor() {
return this.showCaret;
}
/**
* Sets the original format mask. This is necessary because some characters
* in the Forte mask cannot be easily translated to characters in a java, such
* as the "allow incomplete mask" character, "!"
* @param originalFormatText
*/
public void setOriginalFormatText(String pOriginalFormatText) {
this.originalFormatText = pOriginalFormatText;
this.scanFormatForSpecialPatterns(pOriginalFormatText);
}
private void scanFormatForSpecialPatterns(String pFormat) {
if (pFormat == null) {
return;
}
if (this.getFormatter() instanceof NonMandatoryMaskFormatter) {
((NonMandatoryMaskFormatter)this.getFormatter()).setAllowIncompleteValue(false);
}
boolean isExpectingPlaceholderChar = false;
for (int i = 0; i < pFormat.length(); i++) {
switch (pFormat.charAt(i)) {
case '\\':
// Ignore the character after the escape char
i++;
break;
case '!':
// This is an "allow incomplete mask" character, so use it.
if (this.getFormatter() instanceof NonMandatoryMaskFormatter) {
((NonMandatoryMaskFormatter)this.getFormatter()).setAllowIncompleteValue(true);
}
break;
case '<':
isExpectingPlaceholderChar = true;
break;
case '>':
isExpectingPlaceholderChar = false;
break;
default:
if (isExpectingPlaceholderChar) {
if (this.getFormatter() instanceof MaskFormatter) {
((MaskFormatter)this.getFormatter()).setPlaceholderCharacter(pFormat.charAt(i));
}
}
break;
}
}
}
/**
* The isDirty flag returns true if the field has been modified by
* the end user but not committed yet. Should only be called by other
* controls, such as those using a data field as an editor.
* @return
*/
public boolean isDirty() {
return !firstKey;
}
/**
* this method will clearout the actual value of the datafield
* called from DataFieldVerifier.verify to handle those instances
* where the value of a mask field has been removed
*/
public void clearValue() {
if (this.getValue() instanceof TextNullable) {
this.setValue(new TextNullable());
fireAfterValueChangeEvents();
}
// CraigM:26/11/2008 - Allow clearing of TextDatas. DET-55.
else if (this.getValue() instanceof TextData) {
this.setValue(new TextData());
fireAfterValueChangeEvents();
}
else if (this.getValue() instanceof DateTimeNullable) {
this.setValue(new DateTimeNullable());
fireAfterValueChangeEvents();
}
else {
WindowManager.showMessageDialog(
WindowManager.getPrimaryWindow(),
"CodingException: DataFieldVerifier.verify() does not know how to clear out a data-type of " + this.getValue().getClass().getName(),
"Error",
JOptionPane.ERROR_MESSAGE);
}
}
/**
*
* call this method when your datafield has a mask (template) and you want it to be
* able to accept an empty value - by default, a datafield with a mask will NOT allow
* an empty value
*/
public void setAllowsEmptyMasks(boolean value) {
((DataFieldVerifier)this.getInputVerifier()).setAllowsEmptyMasks(value);
}
public DataField cloneComponent(Map<Object, Object> visitedObjects) {
DataField clone = DataFieldFactory.newDataField("", getColumns());
cloneComponentProperties(clone, visitedObjects);
return clone;
}
/**
* Return a clone of this data field, suitable to use for a formatted cell renderer. The
* difference between this and the normal cloneComponent() method is that this method will
* return a data field that has methods overriden for efficiency in a table cell renderer.
* These methods are: <tt>validate</tt> and <tt>revalidate</tt>.
* @return
*/
public DataField cloneComponentForRenderer() {
this.renderer = new DataField() {
// The following methods override the defaults for performance reasons
public void validate() {}
public void revalidate() {}
// Do NOT override these 2 methods, even though you normally can for renderers!
// protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {}
// public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {}
};
cloneComponentProperties(this.renderer, new IdentityHashMap<Object, Object>());
return this.renderer;
}
// PM:31 Oct 2008:
/**
* Return a clone of this data field, suitable to use for a formatted cell renderer.
* @return
*/
public DataField cloneComponentForEditor() {
this.editor = cloneComponent(new IdentityHashMap<Object, Object>());
this.editor.columnTemplate = this;
return editor;
}
private void cloneComponentProperties(DataField clone, Map<Object, Object> visitedObjects) {
// The list of exclusions that will NOT get copied by the clone method
String[] widgetCopyExclusions =
new String[]{"UI", // class javax.swing.plaf.TextUI
"UIClassID", // class java.lang.String
"accessibleContext", // class javax.accessibility.AccessibleContext
"action", // interface javax.swing.Action
"actionCommand", // class java.lang.String
"actionListeners", // class [Ljava.awt.event.ActionListener;
"actionMap", // class javax.swing.ActionMap
"actions", // class [Ljavax.swing.Action;
//"alignmentX", // float
//"alignmentY", // float
//"allowsEmptyMasks", // boolean
"ancestorListeners", // class [Ljavax.swing.event.AncestorListener;
"autoscrolls", // boolean
//"background", // class java.awt.Color
//"border", // interface javax.swing.border.Border
"caret", // interface javax.swing.text.Caret
"caretColor", // class java.awt.Color
"caretListeners", // class [Ljavax.swing.event.CaretListener;
"caretPosition", // int
"cb", // class javax.swing.JComboBox
//"columns", // int
"component", // null
"componentCount", // int
//"componentOrientation", // class java.awt.ComponentOrientation
"componentPopupMenu", // class javax.swing.JPopupMenu
"components", // class [Ljava.awt.Component;
"containerListeners", // class [Ljava.awt.event.ContainerListener;
//"dateFmt", // class java.text.SimpleDateFormat
//"dateTemplate", // class Framework.TextData
//"debugGraphicsOptions", // int
"dirty", // boolean
//"disabledTextColor", // class java.awt.Color
"document", // interface javax.swing.text.Document
//"doubleBuffered", // boolean
//"dragEnabled", // boolean
//"editFormatter", // class javax.swing.JFormattedTextField$AbstractFormatter
//"editValid", // boolean
//"editable", // boolean
"editorComponent", // class java.awt.Component
//"enabled", // boolean
//"filter", // class javax.swing.text.DocumentFilter
"focusAccelerator", // char
"focusCycleRoot", // boolean
"focusLostBehavior", // int
"focusTraversalKeys", // null
"focusTraversalPolicy", // class java.awt.FocusTraversalPolicy
"focusTraversalPolicyProvider", // boolean
"focusTraversalPolicySet", // boolean
//"focusable", // boolean
//"font", // class java.awt.Font
//"foreground", // class java.awt.Color
"formatter", // class javax.swing.JFormattedTextField$AbstractFormatter // TF:24/10/2008:We cannot reuse the formatter, as the data field is installed onto the formatter
"formatterFactory", // class javax.swing.JFormattedTextField$AbstractFormatterFactory // TF:03/11/2008:We cannot reuse the formatter factory as this may store a reference to the widget
"graphics", // class java.awt.Graphics
//"height", // int
"highlighter", // interface javax.swing.text.Highlighter
//"horizontalAlignment", // int
"horizontalVisibility", // interface javax.swing.BoundedRangeModel
//"inheritsPopupMenu", // boolean
"inputMap", // null
"inputMethodRequests", // interface java.awt.im.InputMethodRequests
"inputVerifier", // class javax.swing.InputVerifier
//"insets", // class java.awt.Insets
"item", // class java.lang.Object
"keymap", // interface javax.swing.text.Keymap
"lastValue", // class java.lang.Object
//"layout", // interface java.awt.LayoutManager
"managingFocus", // boolean
"map", // interface DisplayProject.DataMap
//"margin", // class java.awt.Insets
//"maskType", // int
//"maxLength", // int
//"maximumSize", // class java.awt.Dimension
//"minimumSize", // class java.awt.Dimension
// "name", // class java.lang.String
"navigationFilter", // class javax.swing.text.NavigationFilter
"nextFocusableComponent", // class java.awt.Component
//"nullValueAllowed", // boolean
//"opaque", // boolean
//"optimizedDrawingEnabled", // boolean
//"originalFormatText", // class java.lang.String
"paintingTile", // boolean
//"preferredScrollableViewportSize", // class java.awt.Dimension
"preferredSize", // class java.awt.Dimension
"registeredKeyStrokes", // class [Ljavax.swing.KeyStroke;
//"requestFocusEnabled", // boolean
"rootPane", // class javax.swing.JRootPane
"scrollOffset", // int
"scrollableTracksViewportHeight", // boolean
"scrollableTracksViewportWidth", // boolean
"selectedText", // class java.lang.String
"selectedTextColor", // class java.awt.Color
"selectionColor", // class java.awt.Color
"selectionEnd", // int
"selectionStart", // int
"table", // class javax.swing.JTable
"tableColumn", // int
"tableRow", // int
"text", // class java.lang.String
"textValue", // class Framework.TextData
//"toolTipText", // class java.lang.String
"topLevelAncestor", // class java.awt.Container
"transferHandler", // class javax.swing.TransferHandler
//"validateOnKeystroke", // boolean
"validateRoot", // boolean
"value", // class java.lang.Object
"valueNull", // boolean
//"verifyInputWhenFocusTarget", // boolean
"vetoableChangeListeners", // class [Ljava.beans.VetoableChangeListener;
//"visible", // boolean
"visibleRect", // class java.awt.Rectangle
//"width", // int
//"x", // int
//"y" // int
};
CloneHelper.cloneComponent(this, clone, widgetCopyExclusions);
visitedObjects.put(this, clone);
// Must correct the navigation filter prior to correcting the formatter!
AbstractFormatterFactory factory = this.getFormatterFactory();
AbstractFormatter formatter = this.getFormatter();
if (factory instanceof DataFieldFormatterFactory) {
clone.setFormatterFactory(new DataFieldFormatterFactory(
((DataFieldFormatterFactory)factory).getInputMask(),
((DataFieldFormatterFactory)factory).getMaskPlaceholder(),
((DataFieldFormatterFactory)factory).getFormat(),
((DataFieldFormatterFactory)factory).getMappedType()));
}
else if (formatter instanceof NumericFormatter) {
NumericFormatter nf = (NumericFormatter)formatter;
clone.setFormatterFactory(new DefaultFormatterFactory(new NumericFormatter(clone, nf.getOriginalFormat(), nf.getValueClass())));
}
else {
NavigationFilter filter = this.getNavigationFilter();
NavigationFilter newFilter = CloneHelper.deepClone(filter, visitedObjects);
clone.setNavigationFilter(newFilter);
AbstractFormatterFactory newFactory = CloneHelper.deepClone(this.getFormatterFactory(), visitedObjects);
clone.setFormatterFactory(newFactory);
}
// TF:12/02/2009:FTL-9:We need to clone the maxLength of the fixed length document too
if (this.getDocument() instanceof FixedLengthDocument && clone.getDocument() instanceof FixedLengthDocument) {
((FixedLengthDocument)clone.getDocument()).setMaxLength(((FixedLengthDocument)this.getDocument()).getMaxLength());
}
}
/**
* this method adjusts the lead spacing on a DataField to be more like Forte
* This should not be called when a DataField is used as a Combobox Editor
*/
public void customBorder(){
// TF:04/02/2009:Adjusted the border to have an offset of -2 to get them the same
// size as Forte. This used to be in getMinimumSize, but it makes more sense here.
Border newBorder = BorderFactory.createCompoundBorder(this.getBorder(),
BorderFactory.createEmptyBorder(0, LEFT_PAD, -2, 0));
this.setBorder(newBorder);
}
/**
* In Forte, the foreground colour is always the same, irrespective of the state
* the widget is in. This is important in some applications, so we override this
* to enforce this rule.
*/
@Override
public Color getDisabledTextColor() {
//TF:14/8/07:Created this method
return this.getForeground();
}
/**
* If we have a datafield that is sized to Natural and there is a number of columns set we must obey
* the number of columns in preference to the current size.
*/
@Override
public Dimension getMinimumSize() {
Dimension result = super.getMinimumSize();
GridCell cell = GridCell.get(this);
Insets insets = getInsets();
// TF:04/02/2009:Extended this for SP_TO_PARENT and SP_TO_PARTNER
if ((cell.widthPolicy == Constants.SP_NATURAL ||
cell.widthPolicy == Constants.SP_TO_PARENT ||
cell.widthPolicy == Constants.SP_TO_MATRIX_PARTNER||
cell.widthPolicy == Constants.SP_TO_PARTNER) && this.getColumns() > 0) {
// TF:04/02/2009:Corrected this to have the correct size. The +10 is a scaling
// factor that comes from experimentation in Forte to get the correct size. Note
// that we ignore the insets here -- the size of the field is the size of the
// columns * the column width + margin, and different borders do not have an effect
// on the size of the field in Forte, and nor should they here.
result.width = getColumns() * getColumnWidth() + 10;
}
// TF:27/10/2008:All data fields are sized to their minimum size
// Why "+1"? Because this is approximately what forte gave it, so things lay out pretty well
if (fieldHeight <= 0) {
fieldHeight = this.getFontMetrics(this.getFont()).getHeight() + insets.top + insets.bottom + 1;
}
result.height = fieldHeight;
return result;
}
/**
* Override this method to ensure that we get a character size more similar to what Forte gave us
*/
@Override
protected int getColumnWidth() {
if (columnWidth == 0) {
// TF:04/02/2009:Changed this to calculate our own column width to allow the introduction
// of appropriate scaling factors to get the look-and-feel correct.
FontMetrics fm = this.getFontMetrics(this.getFont());
int[] wds = fm.getWidths();
float av = 0;
for (int i = 0; i < wds.length; i++)
av += wds[i];
columnWidth = (int)(av/wds.length * 0.92);
}
return columnWidth;
}
@Override
public void setFont(Font f) {
columnWidth = 0;
fieldHeight = 0;
super.setFont(f);
}
/**
* When printing, we actually want a 2d border, not a 3d one.
*
* @see javax.swing.JComponent#printBorder(java.awt.Graphics)
*
* Written by Craig Mitchell
* @since 07/03/2008
*/
protected void printBorder(Graphics g) {
Border border = getBorder();
if (border != null) {
// If it's a 3d border, switch it to a 2d one
if (border instanceof CompoundBorder) {
border = new LineBorder(Color.black, 1);
}
border.paintBorder(this, g, 0, 0, getWidth(), getHeight());
}
}
//PM:11/04/08 JCT-534
@Override
protected void fireCaretUpdate(CaretEvent e) {
String txt = getSelectedText();
if (txt != null && txt.length() > 0){
ClientEventManager.getTextMenuStatusChangedListener().setSelectedTextState(Constants.MS_ENABLED);
} else {
ClientEventManager.getTextMenuStatusChangedListener().setSelectedTextState(Constants.MS_USAGESTATE);
}
super.fireCaretUpdate(e);
}
/**
* Get the background colour. This obeys the forte-style rules for background colour:<p>
* <ul>
* <li>If an explicit background colour has been set, use that background colour</li>
* <li>Otherwise, if any of our parents have background colour set, use that colour</li>
* <li>Otherwise, use the default colour (white)
* </ul>
*/
@Override
public Color getBackground() {
if (!isBackgroundSet()) {
// We've been set to have a colour of inherit
Color parentColor = UIutils.getParentBackgroundColor(this);
if (parentColor != null) {
return parentColor;
}
else if (getBorder() != null && !(getBorder() instanceof EmptyBorder)){
// There's no parent that's valid, put the default colour of white, but only if there's a border
return Color.white;
}
}
return super.getBackground();
}
/**
* A flag that determines whether calls to setBackground are honoured or not.
*/
private boolean ignoreColourChange = false;
@Override
public void setEnabled(boolean enabled) {
// TF:25/07/2008:By default, the UI sets the background colour of the data field when
// changing the enabled and editable properties. We don't allow this to match Forte's behaviour
this.ignoreColourChange = true;
super.setEnabled(enabled);
//PM:03/11/2008:if this is a cell editor, pass this thru to the clone
if (this.editor != null){
this.editor.setEnabled(enabled);
}
this.ignoreColourChange = false;
}
@Override
public void setEditable(boolean isEditable) {
// TF:25/07/2008:By default, the UI sets the background colour of the data field when
// changing the enabled and editable properties. We don't allow this to match Forte's behaviour
this.ignoreColourChange = true;
super.setEditable(isEditable);
//PM:03/11/2008:if this is a cell editor, pass this thru to the clone
if (this.editor != null){
this.editor.setEditable(isEditable);
}
this.ignoreColourChange = false;
}
@Override
public void setBackground(Color bg) {
// TF:25/07/2008:By default, the UI sets the background colour of the data field when
// changing the enabled and editable properties. We don't allow this to match Forte's behaviour
if (!this.ignoreColourChange) {
super.setBackground(bg);
}
}
@Override
public void setForeground(Color bg) {
// TF:25/07/2008:By default, the UI sets the foreground colour of the data field when
// changing the enabled and editable properties. We don't allow this to match Forte's behaviour
if (!this.ignoreColourChange) {
super.setForeground(bg);
}
}
/**
* Get the foreground colour. This obeys the forte-style rules for foreground colour:<p>
* <ul>
* <li>If an explicit foreground colour has been set, use that foreground colour</li>
* <li>Otherwise, if any of our parents have foreground colour set, use that colour</li>
* <li>Otherwise, use the default colour (black)
* </ul>
*/
@Override
public Color getForeground() {
if (!isForegroundSet()) {
// We've been set to have a colour of inherit
Container parent = this.getParent();
// Keep going up until we find a parent with the background set. We don't care about
// JScrollPanes or JViewports, because these should derive their colour from the control,
// not the other way around
while (parent != null && (!parent.isForegroundSet() || parent instanceof JScrollPane || parent instanceof JViewport)) {
parent = parent.getParent();
}
if (parent != null && !(parent instanceof Window)) {
return parent.getForeground();
}
else {
// There's no parent that's valid, return the default colour of black
return Color.black;
}
}
return super.getForeground();
}
/**
* Get the value of the data field as a decimal nullable. This method was undocumented in Forte
* @return
*/
public DecimalNullable getDataNumeric() {
try {
return new DecimalNullable(Double.parseDouble(this.getText()), 4, DecimalNullable.qq_Resolver.cDOUBLEVALUE_SCALE);
}
catch (Exception e) {
return new DecimalNullable(true, 4, DecimalNullable.qq_Resolver.cISNULL_SCALE);
}
}
/**
* Returns the cloned editor component, null if there is none
* @return
*/
public DataField getEditor(){
return this.editor;
}
/**
* Returns the original template if this is a cloned editor component
* @return
*/
public DataField getColumnTemplate(){
return this.columnTemplate;
}
/**
* Get the component that is the column template for this array field. If there is no
* column template, or this data field is not in an array, return <tt>this</tt>
* @return
*/
public JComponent correctPostingComponent(){
// PM:31 Oct 2008:this caters for a datafield being used as a column template
if (this.columnTemplate != null){
return this.columnTemplate;
} else {
return this;
}
}
/**
* Get the component which is actually used to edit the values in an array field. This is
* not the column template, but rather the actual editor. If there is no editor, or this
* data field is not in an array, return <tt>this</tt>
* @return
*/
public JComponent correctSubscriptionComponent(){
if (this.editor != null){
return this.editor;
} else {
return this;
}
}
/**
* Override the tooltip text so that if it's being set to "" then we actually set the
* value to null to remove the tooltip text, as this is how Forte behaved.
*/
@Override
public void setToolTipText(String pText) {
if (pText == null || pText.equals("")) {
super.setToolTipText(null);
}
else {
super.setToolTipText(pText);
}
}
}