package net.sf.openrocket.gui.components; import java.awt.Color; import java.awt.Dimension; import java.awt.ItemSelectable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.EventObject; import java.util.List; import javax.swing.Action; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import net.sf.openrocket.gui.adaptors.DoubleModel; import net.sf.openrocket.unit.Unit; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.StateChangeListener; /** * A Swing component that allows one to choose a unit from a UnitGroup within * a DoubleModel model. The current unit of the model is shown as a JLabel, and * the unit can be changed by clicking on the label. * * @author Sampo Niskanen <sampo.niskanen@iki.fi> */ public class UnitSelector extends StyledLabel implements StateChangeListener, MouseListener, ItemSelectable { private DoubleModel model; private final Action[] extraActions; private UnitGroup unitGroup; private Unit currentUnit; private final boolean showValue; private final Border normalBorder; private final Border withinBorder; private final List<ItemListener> itemListeners = new ArrayList<ItemListener>(); /** * Common private constructor that sets the values and sets up the borders. * Either model or group must be null. * * @param model * @param showValue * @param group * @param actions */ private UnitSelector(DoubleModel model, boolean showValue, UnitGroup group, Action[] actions) { super(); this.model = model; this.showValue = showValue; if (model != null) { this.unitGroup = model.getUnitGroup(); this.currentUnit = model.getCurrentUnit(); } else if (group != null) { this.unitGroup = group; this.currentUnit = group.getDefaultUnit(); } else { this.unitGroup = UnitGroup.UNITS_NONE; this.currentUnit = UnitGroup.UNITS_NONE.getDefaultUnit(); } this.extraActions = actions; addMouseListener(this); // Define borders to use: normalBorder = new CompoundBorder( new LineBorder(new Color(0f, 0f, 0f, 0.08f), 1), new EmptyBorder(1, 1, 1, 1)); withinBorder = new CompoundBorder(new LineBorder(new Color(0f, 0f, 0f, 0.6f)), new EmptyBorder(1, 1, 1, 1)); // Add model listener if showing value if (showValue) this.model.addChangeListener(this); setBorder(normalBorder); updateText(); } public UnitSelector(DoubleModel model, Action... actions) { this(model, false, actions); } public UnitSelector(DoubleModel model, boolean showValue, Action... actions) { this(model, showValue, null, actions); } public UnitSelector(UnitGroup group, Action... actions) { this(null, false, group, actions); } /** * Return the DoubleModel that is backing this selector up, or <code>null</code>. * Either this method or {@link #getUnitGroup()} always returns <code>null</code>. * * @return the DoubleModel being used, or <code>null</code>. */ public DoubleModel getModel() { return model; } /** * Set the current double model. * * @param model the model to set, <code>null</code> is NOT allowed. */ public void setModel(DoubleModel model) { if (this.model != null && showValue) { this.model.removeChangeListener(this); } this.model = model; this.unitGroup = model.getUnitGroup(); this.currentUnit = model.getCurrentUnit(); if (showValue) { this.model.addChangeListener(this); } updateText(); } /** * Return the unit group that is being shown, or <code>null</code>. Either this method * or {@link #getModel()} always returns <code>null</code>. * * @return the UnitGroup being used, or <code>null</code>. */ public UnitGroup getUnitGroup() { return unitGroup; } public void setUnitGroup(UnitGroup group) { if (model != null) { throw new IllegalStateException( "UnitGroup cannot be set when backed up with model."); } if (this.unitGroup == group) return; this.unitGroup = group; this.currentUnit = group.getDefaultUnit(); updateText(); } /** * Return the currently selected unit. Works both when backup up with a DoubleModel * and UnitGroup. * * @return the currently selected unit. */ public Unit getSelectedUnit() { return currentUnit; } /** * Set the currently selected unit. Sets it to the DoubleModel if it is backed up * by it. * * @param unit the unit to select. */ public void setSelectedUnit(Unit unit) { if (!unitGroup.contains(unit)) { throw new IllegalArgumentException("unit " + unit + " not contained in group " + unitGroup); } this.currentUnit = unit; if (model != null) { model.setCurrentUnit(unit); } updateText(); fireItemEvent(); } /** * Updates the text of the label */ private void updateText() { if (model != null) { Unit unit = model.getCurrentUnit(); if (showValue) { setText(unit.toStringUnit(model.getValue())); } else { setText(unit.getUnit()); } } else if (unitGroup != null) { setText(currentUnit.getUnit()); } else { throw new IllegalStateException("Both model and unitGroup are null."); } } /** * Update the component when the DoubleModel changes. */ @Override public void stateChanged(EventObject e) { updateText(); } //////// ItemListener handling //////// @Override public void addItemListener(ItemListener listener) { itemListeners.add(listener); } @Override public void removeItemListener(ItemListener listener) { itemListeners.remove(listener); } protected void fireItemEvent() { ItemEvent event = null; ItemListener[] listeners = itemListeners.toArray(new ItemListener[0]); for (ItemListener l: listeners) { if (event == null) { event = new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, getSelectedUnit(), ItemEvent.SELECTED); } l.itemStateChanged(event); } } //////// Popup //////// private void popup() { JPopupMenu popup = new JPopupMenu(); for (int i = 0; i < unitGroup.getUnitCount(); i++) { Unit unit = unitGroup.getUnit(i); JMenuItem item = new JMenuItem(unit.getUnit()); item.addActionListener(new UnitSelectorItem(unit)); popup.add(item); } for (int i = 0; i < extraActions.length; i++) { if (extraActions[i] == null && i < extraActions.length - 1) { popup.addSeparator(); } else { popup.add(new JMenuItem(extraActions[i])); } } Dimension d = getSize(); popup.show(this, 0, d.height); } /** * ActionListener class that sets the currently selected unit. */ private class UnitSelectorItem implements ActionListener { private final Unit unit; public UnitSelectorItem(Unit u) { unit = u; } @Override public void actionPerformed(ActionEvent e) { setSelectedUnit(unit); } } @Override public Object[] getSelectedObjects() { return new Object[]{ getSelectedUnit() }; } //////// Mouse handling //////// @Override public void mouseClicked(MouseEvent e) { if (unitGroup.getUnitCount() > 1) popup(); } @Override public void mouseEntered(MouseEvent e) { if (unitGroup.getUnitCount() > 1) setBorder(withinBorder); } @Override public void mouseExited(MouseEvent e) { setBorder(normalBorder); } @Override public void mousePressed(MouseEvent e) { } // Ignore @Override public void mouseReleased(MouseEvent e) { } // Ignore }