/*
* Copyright (c) 2007, 2015 , Oracle. All rights reserved.
*
* This software is the proprietary information of Oracle Corporation.
* Use is subject to license terms.
*/
package org.eclipse.persistence.tools.workbench.uitools.swing;
import java.awt.Color;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import org.eclipse.persistence.tools.workbench.uitools.app.PropertyValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.TransformationPropertyValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.ValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.swing.DocumentAdapter;
import org.eclipse.persistence.tools.workbench.uitools.cell.AbstractCellRendererAdapter;
import org.eclipse.persistence.tools.workbench.uitools.cell.CellRendererAdapter;
import org.eclipse.persistence.tools.workbench.utility.string.StringTools;
/**
* This handler is responsible to update the text field by showing a default
* value if one is specified and when there is no entry. The default value is
* either removed on focus gained or added on focus lost if and only if there is
* no entry. The default value is shown as grayed out and within parenthesis.
*
* @version 11.0.0
* @since 11.0.0
* @author Pascal Filion
*/
@SuppressWarnings("nls")
public final class TextFieldWithDefaultHandler
{
/**
* This <code>CellRendererAdapter</code> is responsible to format the string
* holded by the <code>PropertyValueModel</code>.
*/
private CellRendererAdapter cellRendererAdapter;
/**
* The holder of the default value.
*/
private ValueModel defaultValueHolder;
/**
* Flag used to prevent this widget to push any value to the value holder
* when the default value has to be set or cleared from the text field.
*/
private boolean locked;
/**
* Flag used to show the default value when the spinner loses the focus or
* when the subject holder's value changes and receives null.
*/
private boolean showDefaultValue;
/**
* The holder of the subject, which is used to either clear the text field or
* show the default value.
*/
private ValueModel subjectHolder;
/**
* The text field that will be used to show the default value when the value
* holder contains <code>null</code>.
*/
private JTextField textField;
/**
* This flat is used to determine if the property change listener listening
* to the subject should update the spinner or not. Sometimes, depending on
* the other of the events, the spinner is synchronized before the listener
* is notified. This will make sure the listener isn't overriding the value
* set by the spinner.
*/
private boolean valueSet;
/**
* Creates a new <code>TextFieldWithDefault</code>.
*
* @param textField The text field that will be used to show the default
* value when the value holder contains <code>null</code>
* @param subjectHolder The holder of the subject, which is used to either
* clear the text field or show the default value
* @param valueHolder The <code>PropertyValueModel</code> listening to the
* property, or <code>null</code> if the value does not need to be updated
* through the text field's document
* @param defaultValueHolder The holder of the default value
*/
public TextFieldWithDefaultHandler(JTextField textField,
ValueModel subjectHolder,
PropertyValueModel valueHolder,
ValueModel defaultValueHolder)
{
this(textField, subjectHolder, valueHolder, defaultValueHolder, null);
}
/**
* Creates a new <code>TextFieldWithDefault</code>.
*
* @param textField The text field that will be used to show the default
* value when the value holder contains <code>null</code>
* @param subjectHolder The holder of the subject, which is used to either
* clear the text field or show the default value
* @param valueHolder The <code>PropertyValueModel</code> listening to the
* property, or <code>null</code> if the value does not need to be updated
* through the text field's document
* @param defaultValueHolder The holder of the default value
* @param cellRendererAdapter The <code>CellRendererAdapter</code> is
* responsible to format the string holded by the <code>PropertyValueModel</code>
*/
public TextFieldWithDefaultHandler(JTextField textField,
ValueModel subjectHolder,
PropertyValueModel valueHolder,
ValueModel defaultValueHolder,
CellRendererAdapter cellRendererAdapter)
{
super();
initialize
(
textField,
subjectHolder,
valueHolder,
defaultValueHolder,
cellRendererAdapter
);
}
private CellRendererAdapter buildCellRendererAdapter()
{
return new AbstractCellRendererAdapter()
{
@Override
public String buildText(Object value)
{
return (String)value;
}
};
}
private Document buildDocument(PropertyValueModel valueHolder,
Document originalDocument)
{
return new DocumentAdapter
(
buildValueHolder(valueHolder),
originalDocument
);
}
private FocusListener buildFocusListener()
{
return new FocusListener()
{
public void focusGained(FocusEvent e)
{
if (!e.isTemporary())
{
updateTextFieldOnFocusGained();
}
}
public void focusLost(FocusEvent e)
{
if (!e.isTemporary())
{
updateTextFieldOnFocusLost();
}
}
};
}
private PropertyChangeListener buildPropertyChangeListener()
{
return new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent e)
{
updateTextFieldForegroundColor((String) e.getNewValue());
}
};
}
private PropertyChangeListener buildSubjectPropertyChangeListener()
{
return new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e)
{
if (locked)
return;
try
{
locked = true;
showDefaultValue = (subjectHolder.getValue() == null);
if (showDefaultValue)
{
valueSet = false;
}
if (!valueSet)
{
String defaultValue = (String)defaultValue();
textField.setText(defaultValue);
updateTextFieldForegroundColor(defaultValue);
}
}
finally
{
locked = false;
valueSet = false;
showDefaultValue = false;
}
}};
}
private PropertyValueModel buildValueHolder(PropertyValueModel valueHolder)
{
PropertyValueModel holder = new InternalPropertyValueModel(valueHolder);
holder.addPropertyChangeListener(ValueModel.VALUE, buildPropertyChangeListener());
return holder;
}
/**
* Returns the default value stored in the default value holder.
*
* @return The default value returned by the default value holder,
* <code>null</code> is never returned
*/
private Object defaultValue()
{
// The subject has been disconnected, don't use the default value
if (subjectHolder.getValue() == null)
{
return "";
}
Object defaultValue = defaultValueHolder.getValue();
// No default value specified
if (StringTools.stringIsEmpty((String)defaultValue))
{
return "";
}
StringBuilder sb = new StringBuilder();
sb.append('(');
sb.append(defaultValue);
sb.append(')');
return sb.toString();
}
/**
* Returns the <code>JTextField</code> that was given to this handler.
*
* @return The widget being used to show a default value
*/
public final JTextField getTextField()
{
return textField;
}
/**
* Initializes this text field.
*
* @param textField The text field that will be used to show the default
* value when the value holder contains <code>null</code>
* @param subjectHolder
* @param valueHolder The <code>PropertyValueModel</code> listening to the
* property, or <code>null</code> if the value does not need to be updated
* through the text field's document
* @param defaultValueHolder The holder of the default value
* @param cellRendererAdapter The <code>CellRendererAdapter</code>
* responsible to format the string holded by the <code>PropertyValueModel</code>
*/
private void initialize(JTextField textField,
ValueModel subjectHolder,
PropertyValueModel valueHolder,
ValueModel defaultValueHolder,
CellRendererAdapter cellRendererAdapter)
{
this.textField = textField;
this.defaultValueHolder = defaultValueHolder;
this.subjectHolder = subjectHolder;
this.cellRendererAdapter = (cellRendererAdapter != null) ? cellRendererAdapter : buildCellRendererAdapter();
this.subjectHolder.addPropertyChangeListener(ValueModel.VALUE, buildSubjectPropertyChangeListener());
if (valueHolder != null)
{
// The document needs to be detached from the text field before the
// DocumentAdapter can be set since it's using the old document as the
// delegate and this will cause a dead lock
Document document = buildDocument(valueHolder, textField.getDocument());
textField.setDocument(new PlainDocument());
textField.setDocument(document);
}
textField.addFocusListener(buildFocusListener());
updateTextFieldForegroundColor(textField.getText());
}
/**
* Updates the foreground color based on the given text. If the text is the
* default value then the foreground color is changed to grey.
*
* @param text The current value
*/
private void updateTextFieldForegroundColor(String text)
{
if (defaultValue().equals(text))
{
textField.setForeground(Color.GRAY);
}
else
{
textField.setForeground(UIManager.getColor("TextField.foreground"));
}
}
/**
* Clears the default value if the text field contains it.
*/
private void updateTextFieldOnFocusGained()
{
if (textField.getText().equals(defaultValue()))
{
locked = true;
try
{
textField.setText("");
textField.setForeground(UIManager.getColor("TextField.foreground"));
}
finally
{
locked = false;
}
}
}
/**
* Show the default value if the text field contains an empty entry.
*/
private void updateTextFieldOnFocusLost()
{
if (textField.getText().trim().length() == 0)
{
locked = true;
try
{
textField.setText((String)defaultValue());
textField.setForeground(Color.GRAY);
}
finally
{
locked = false;
}
}
}
/**
* This <code>PropertyValueModel</code> is responsible to format the actual
* value before returning it. The value will not be given to the nested
* <code>PropertyValueModel</code> when the lock is on.
*/
private final class InternalPropertyValueModel extends TransformationPropertyValueModel
{
/**
* Creates a new <code>InternalPropertyValueModel</code>.
*
* @param valueHolder The nested <code>PropertyValueModel</code>
*/
InternalPropertyValueModel(PropertyValueModel valueHolder)
{
super(valueHolder);
}
private String formatNewValue(String value)
{
return (value == null) ? null : cellRendererAdapter.buildText(value);
}
/*
* (non-Javadoc)
*/
@Override
protected Object reverseTransform(Object value)
{
return defaultValue().equals(value) ? null : value;
}
/*
* (non-Javadoc)
*/
@Override
public void setValue(Object value)
{
if (!locked)
{
super.setValue(value);
}
}
/*
* (non-Javadoc)
*/
@Override
protected Object transform(Object value)
{
// Show the default value
if (showDefaultValue)
{
return defaultValue();
}
// During editing, don't change the null value to the default value
return (textField.hasFocus() || (value != null)) ?
formatNewValue((String)value) : defaultValue();
}
/*
* (non-Javadoc)
*/
@Override
protected void valueChanged(PropertyChangeEvent e)
{
valueSet = true;
super.valueChanged(e);
}
}
}