/**
*
*/
package cz.cuni.mff.peckam.java.origamist.gui.common;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Vector;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.origamist.JLocalizedButton;
import javax.swing.origamist.JLocalizedLabel;
import javax.swing.origamist.UnitListCellRenderer;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import cz.cuni.mff.peckam.java.origamist.configuration.Configuration;
import cz.cuni.mff.peckam.java.origamist.model.UnitDimension;
import cz.cuni.mff.peckam.java.origamist.model.jaxb.Unit;
import cz.cuni.mff.peckam.java.origamist.services.ServiceLocator;
import cz.cuni.mff.peckam.java.origamist.services.TooltipFactory;
import cz.cuni.mff.peckam.java.origamist.services.interfaces.ConfigurationManager;
/**
* A input element for {@link cz.cuni.mff.peckam.java.origamist.model.UnitDimension}.
*
* @author Martin Pecka
*/
/**
*
*
* @author Martin Pecka
*/
public class JUnitDimensionInput extends JPanel
{
/** */
private static final long serialVersionUID = 3259320166895060569L;
/** Aspect ratio of the entered dimension. If this is 0, the ratio is undefined. */
protected double aspectRatio = 0d;
/** The value of this input. */
protected UnitDimension value = new UnitDimension();
/**
* If <code>true</code>, setting values to width or height won't affect the other even if the preserve ratio
* checkbox is checked.
*/
protected boolean ignoreAscpectRatio = false;
/**
* If <code>true</code>, setting the value fro width and height components only sets its value without performing
* actions in the custom change listners.
*/
protected boolean setRawValue = false;
/** The list of change listeners. */
protected List<ChangeListener> changeListeners = new LinkedList<ChangeListener>();
/** It <code>true</code>, the change listeners are not notified of changes. */
protected boolean disableChangeListeners = false;
/**
* If <code>true</code>, setValue() will just set the value and will do no other side-effects. Not used in this
* class, but can be handy in subclasses.
*/
protected boolean setValueWithoutSideEffects = false;
/** Input for width. */
protected JSpinner width;
/** Input for height. */
protected JSpinner height;
/** Input for unit. */
protected JComboBox unit;
/** Checkbox for selecting, whether the aspect ratio should be preserved. */
protected JCheckBox preserveRatio;
/** Input for reference length. */
protected JSpinner refLength;
/** Input for reference unit. */
protected JComboBox refUnit;
/** The label displaying the current aspect ratio. */
protected JLabel aspectRatioDisplay;
/** The button for rotating the paper 90 degrees. */
protected JLocalizedButton rotatePaper;
/** Label. */
protected JLabel widthLabel, heightLabel, unitLabel, refLabel, refLengthLabel, refUnitLabel,
aspectRatioLabel;
/** Listener for aspect ratio changes. */
protected ChangeListener aspectRatioListener;
/**
*
*/
public JUnitDimensionInput()
{
createComponents();
buildLayout();
ItemEvent unitEvent = new ItemEvent(unit, 0, unit.getSelectedItem(), ItemEvent.SELECTED);
for (ItemListener l : unit.getItemListeners()) {
l.itemStateChanged(unitEvent);
}
ItemEvent refUnitEvent = new ItemEvent(refUnit, 0, refUnit.getSelectedItem(), ItemEvent.SELECTED);
for (ItemListener l : refUnit.getItemListeners()) {
l.itemStateChanged(refUnitEvent);
}
aspectRatioListener.stateChanged(new ChangeEvent(this));
}
/**
* Create and setup all the components.
*/
protected void createComponents()
{
Unit prefUnit = ServiceLocator.get(ConfigurationManager.class).get().getPreferredUnit();
widthLabel = new JLocalizedLabel("application", "JUnitDimensionInput.width");
heightLabel = new JLocalizedLabel("application", "JUnitDimensionInput.height");
unitLabel = new JLocalizedLabel("application", "JUnitDimensionInput.unit");
refLabel = new JLocalizedLabel("application", "JUnitDimensionInput.refLabel");
refLengthLabel = new JLocalizedLabel("application", "JUnitDimensionInput.refLength");
refUnitLabel = new JLocalizedLabel("application", "JUnitDimensionInput.refUnit");
aspectRatioLabel = new JLocalizedLabel("application", "JUnitDimensionInput.aspectRatioLabel");
aspectRatioDisplay = new JLabel();
aspectRatioListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e)
{
NumberFormat format = NumberFormat.getInstance(ServiceLocator.get(ConfigurationManager.class).get()
.getLocale());
format.setMaximumFractionDigits(5);
format.setMinimumFractionDigits(1);
aspectRatioDisplay.setText(format.format(aspectRatio));
aspectRatioDisplay.repaint();
}
};
width = new JSpinner(new SpinnerNumberModel(0.0d, 0.0d, null, 0.1d));
final JSpinner.NumberEditor widthEditor = new JSpinner.NumberEditor(width, "0.0######");
widthEditor.getTextField().setColumns(9);
width.setEditor(widthEditor);
width.setValue(0.0d);
value.setWidth(0.0d);
width.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e)
{
Double newVal = (Double) width.getValue();
if (setRawValue) {
value.setWidth(newVal);
return;
}
boolean refRescaled = false;
if (value.getUnit() == Unit.REL) {
newVal /= 100d;
if (newVal > 1.0d) {
refRescaled = true;
double ratio = 1.0d / newVal;
newVal = 1.0d;
if (!ignoreAscpectRatio && preserveRatio.isSelected() && aspectRatio > 0.0d) {
refLength.setValue(value.getReferenceLength() / ratio);
setRawValue = true;
width.setValue(100d);
setRawValue = false;
} else {
double newHeight = value.getHeight() * ratio;
double newRefLength = value.getReferenceLength() / ratio;
ignoreAscpectRatio = true;
height.setValue(newHeight * 100d);
ignoreAscpectRatio = false;
refLength.setValue(newRefLength);
setRawValue = true;
width.setValue(100d);
setRawValue = false;
}
}
}
if (newVal != value.getWidth() || refRescaled) {
value.setWidth(newVal);
boolean oldDisable = disableChangeListeners;
disableChangeListeners = true;
if (!ignoreAscpectRatio && preserveRatio.isSelected() && aspectRatio > 0.0d && !refRescaled) {
double newHeight = (value.getUnit() != Unit.REL ? newVal : (newVal * 100d)) / aspectRatio;
ignoreAscpectRatio = true;
height.setValue(newHeight);
ignoreAscpectRatio = false;
}
disableChangeListeners = oldDisable;
notifyChangeListeners();
}
if (value.getWidth() == 0.0d || value.getHeight() == 0.0d) {
aspectRatio = 0.0d;
} else {
aspectRatio = value.getWidth() / value.getHeight();
}
aspectRatioListener.stateChanged(e);
}
});
height = new JSpinner(new SpinnerNumberModel(0.0d, 0.0d, null, 0.1d));
final JSpinner.NumberEditor heightEditor = new JSpinner.NumberEditor(height, "0.0######");
heightEditor.getTextField().setColumns(9);
height.setEditor(heightEditor);
height.setValue(0.0d);
value.setHeight(0.0d);
height.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e)
{
Double newVal = (Double) height.getValue();
if (setRawValue) {
value.setHeight(newVal);
return;
}
boolean refRescaled = false;
if (value.getUnit() == Unit.REL) {
newVal /= 100d;
if (newVal > 1.0d) {
refRescaled = true;
double ratio = 1.0d / newVal;
newVal = 1.0d;
if (!ignoreAscpectRatio && preserveRatio.isSelected() && aspectRatio > 0.0d) {
refLength.setValue(value.getReferenceLength() / ratio);
setRawValue = true;
height.setValue(100d);
setRawValue = false;
} else {
double newWidth = value.getWidth() * ratio;
double newRefLength = value.getReferenceLength() / ratio;
ignoreAscpectRatio = true;
width.setValue(newWidth * 100d);
ignoreAscpectRatio = false;
refLength.setValue(newRefLength);
setRawValue = true;
height.setValue(100d);
setRawValue = false;
}
}
}
if (newVal != value.getHeight() || refRescaled) {
value.setHeight(newVal);
boolean oldDisable = disableChangeListeners;
disableChangeListeners = true;
if (!ignoreAscpectRatio && preserveRatio.isSelected() && aspectRatio > 0.0d && !refRescaled) {
double newWidth = (value.getUnit() != Unit.REL ? newVal : (newVal * 100d)) * aspectRatio;
ignoreAscpectRatio = true;
width.setValue(newWidth);
ignoreAscpectRatio = false;
}
disableChangeListeners = oldDisable;
notifyChangeListeners();
}
if (value.getWidth() == 0.0d || value.getHeight() == 0.0d) {
aspectRatio = 0.0d;
} else {
aspectRatio = value.getWidth() / value.getHeight();
}
aspectRatioListener.stateChanged(e);
}
});
unit = new JComboBox(getUnits(true));
unit.setRenderer(new UnitListCellRenderer());
unit.setSelectedItem(prefUnit != null ? prefUnit : unit.getItemAt(0));
value.setUnit((Unit) unit.getSelectedItem());
ItemListener unitItemListener = new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e)
{
if (e.getStateChange() == ItemEvent.SELECTED) {
Unit newUnit = (Unit) e.getItem();
boolean isRel = newUnit == Unit.REL;
UnitDimension newValue = value.convertTo(newUnit);
setValueWithoutSideEffects = true;
setValue(newValue);
setValueWithoutSideEffects = false;
refLength.setVisible(isRel);
refLengthLabel.setVisible(isRel);
refUnit.setVisible(isRel);
refUnitLabel.setVisible(isRel);
refLabel.setVisible(isRel);
invalidate();
}
}
};
unit.addItemListener(unitItemListener);
preserveRatio = new JCheckBox();
ServiceLocator
.get(ConfigurationManager.class)
.get()
.addAndRunResourceBundleListener(
new Configuration.LocaleListener("application", "JUnitDimensionInput.preserveRatio") {
@Override
protected void updateText(String text)
{
preserveRatio.setText(text);
preserveRatio.setToolTipText(ServiceLocator.get(TooltipFactory.class).getPlain(text));
}
});
rotatePaper = new JLocalizedButton("application", "JUnitDimensionInput.rotatePaperLabel");
rotatePaper.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e)
{
UnitDimension value = getValue();
double height = value.getHeight();
value.setHeight(value.getWidth());
value.setWidth(height);
setValue(value);
}
});
refLength = new JSpinner(new SpinnerNumberModel(0.0d, 0.0d, null, 0.1d));
JSpinner.NumberEditor refLengthEditor = new JSpinner.NumberEditor(refLength, "0.0######");
refLengthEditor.getTextField().setColumns(9);
refLength.setEditor(refLengthEditor);
refLength.setValue(1.0d);
refLength.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e)
{
value.setReference(value.getReferenceUnit(), (Double) refLength.getValue());
notifyChangeListeners();
}
});
refUnit = new JComboBox(getUnits(false));
refUnit.setSelectedItem(prefUnit != null && !prefUnit.equals(Unit.REL) ? prefUnit : refUnit.getItemAt(0));
refUnit.setRenderer(new UnitListCellRenderer());
ItemListener refUnitItemListener = new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e)
{
if (e.getStateChange() == ItemEvent.SELECTED) {
value.setReference((Unit) refUnit.getSelectedItem(), value.getReferenceLength());
notifyChangeListeners();
}
}
};
refUnit.addItemListener(refUnitItemListener);
value.setReference((Unit) refUnit.getSelectedItem(), (Double) refLength.getValue());
PropertyChangeListener localeListener = new PropertyChangeListener() {;
@Override
public void propertyChange(PropertyChangeEvent evt)
{
//TODO this should localize the spinners, but it doesn't work...
Locale l = (Locale) evt.getNewValue();
widthEditor.getFormat().setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(l));
heightEditor.getFormat().setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(l));
}
};
Configuration conf = ServiceLocator.get(ConfigurationManager.class).get();
conf.addPropertyChangeListener("locale", localeListener);
localeListener.propertyChange(new PropertyChangeEvent(this, "locale", null, conf.getLocale()));
}
/**
* Add all components to layout.
*/
protected void buildLayout()
{
setLayout(new FormLayout("pref,$lcgap,min(60dlu;pref),$ugap,pref,$lcgap,pref",
"pref,$lgap,pref,$lgap,pref,$lgap,pref,$lgap,pref"));
CellConstraints cc = new CellConstraints();
add(widthLabel, cc.xy(1, 1));
add(width, cc.xy(3, 1));
add(heightLabel, cc.xy(1, 3));
add(height, cc.xy(3, 3));
add(unitLabel, cc.xy(5, 1));
add(unit, cc.xy(7, 1));
add(preserveRatio, cc.xyw(5, 3, 3));
add(aspectRatioLabel, cc.xy(1, 5));
add(aspectRatioDisplay, cc.xy(3, 5));
add(rotatePaper, cc.xyw(5, 5, 3));
add(refLabel, cc.xyw(1, 7, 5));
add(refLengthLabel, cc.xy(1, 9));
add(refLength, cc.xy(3, 9));
add(refUnitLabel, cc.xy(5, 9));
add(refUnit, cc.xy(7, 9));
}
/**
* Returns a vector of all units.
*
* @param includeRel If <code>false</code>, the <code>Unit.REL</code> won't appear in the returned list.
* @return A vector of all units.
*/
protected Vector<Unit> getUnits(boolean includeRel)
{
Vector<Unit> units = new Vector<Unit>(Arrays.asList(Unit.values()));
if (!includeRel)
units.remove(Unit.REL);
return units;
}
/**
* @return The copy of the value.
*/
public UnitDimension getValue()
{
UnitDimension result = new UnitDimension();
result.setWidth(value.getWidth());
result.setHeight(value.getHeight());
result.setUnit(value.getUnit());
if (value.getUnit().equals(Unit.REL)) {
result.setReference(value.getReferenceUnit(), value.getReferenceLength());
}
return result;
}
/**
* @param value The value which is used to set this input's value.
*/
public void setValue(UnitDimension value)
{
if (value.getUnit() == null)
value.setUnit(Unit.values()[0]);
disableChangeListeners = true;
Unit oldUnit = this.value.getUnit();
this.value = value;
if (value.getReferenceLength() != null && value.getReferenceUnit() != null) {
refLength.setValue(value.getReferenceLength());
refUnit.setSelectedItem(value.getReferenceUnit());
}
value.setReference((Unit) refUnit.getSelectedItem(), (Double) refLength.getValue());
unit.setSelectedItem(value.getUnit());
ignoreAscpectRatio = true;
if (value.getUnit() != Unit.REL) {
width.setValue(value.getWidth());
height.setValue(value.getHeight());
} else {
double newWidth = value.getWidth();
double newHeight = value.getHeight();
// if we convert between a relative and relative value, we must normalize the value
if (oldUnit == Unit.REL) {
newWidth /= 100d;
newHeight /= 100d;
}
if (newWidth > 1.0d || newHeight > 1.0d) {
double ratio = 1.0d / Math.max(newWidth, newHeight);
newWidth *= ratio;
newHeight *= ratio;
refLength.setValue(value.getReferenceLength() / ratio);
}
this.value.setWidth(newWidth);
this.value.setHeight(newHeight);
setRawValue = true;
width.setValue(newWidth * 100d);
height.setValue(newHeight * 100d);
setRawValue = false;
}
ignoreAscpectRatio = false;
disableChangeListeners = false;
notifyChangeListeners();
}
@Override
public void setEnabled(boolean enabled)
{
super.setEnabled(enabled);
width.setEnabled(enabled);
height.setEnabled(enabled);
unit.setEnabled(enabled);
preserveRatio.setEnabled(enabled);
refLength.setEnabled(enabled);
refUnit.setEnabled(enabled);
}
/**
* Add a change listener.
*
* @param listener The listener to add.
*/
public void addChangeListener(ChangeListener listener)
{
changeListeners.add(listener);
}
/**
* Remove the given change listener.
*
* @param listener The listener to remove.
*/
public void removeChangeListener(ChangeListener listener)
{
changeListeners.remove(listener);
}
/**
* Notify change listeners that a value has changed.
*/
protected void notifyChangeListeners()
{
if (disableChangeListeners)
return;
ChangeEvent e = new ChangeEvent(this);
for (ChangeListener l : changeListeners) {
l.stateChanged(e);
}
}
}