create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / components / UnitSelector.java
1 package net.sf.openrocket.gui.components;
2
3
4 import java.awt.Color;
5 import java.awt.Dimension;
6 import java.awt.ItemSelectable;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.awt.event.ItemEvent;
10 import java.awt.event.ItemListener;
11 import java.awt.event.MouseEvent;
12 import java.awt.event.MouseListener;
13 import java.util.ArrayList;
14 import java.util.EventObject;
15 import java.util.List;
16
17 import javax.swing.Action;
18 import javax.swing.JMenuItem;
19 import javax.swing.JPopupMenu;
20 import javax.swing.border.Border;
21 import javax.swing.border.CompoundBorder;
22 import javax.swing.border.EmptyBorder;
23 import javax.swing.border.LineBorder;
24
25 import net.sf.openrocket.gui.adaptors.DoubleModel;
26 import net.sf.openrocket.unit.Unit;
27 import net.sf.openrocket.unit.UnitGroup;
28 import net.sf.openrocket.util.StateChangeListener;
29
30
31 /**
32  * A Swing component that allows one to choose a unit from a UnitGroup within
33  * a DoubleModel model.  The current unit of the model is shown as a JLabel, and
34  * the unit can be changed by clicking on the label.
35  * 
36  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
37  */
38
39 public class UnitSelector extends StyledLabel implements StateChangeListener, MouseListener,
40                 ItemSelectable {
41
42         private DoubleModel model;
43         private final Action[] extraActions;
44
45         private UnitGroup unitGroup;
46         private Unit currentUnit;
47
48         private final boolean showValue;
49
50         private final Border normalBorder;
51         private final Border withinBorder;
52
53
54         private final List<ItemListener> itemListeners = new ArrayList<ItemListener>();
55
56
57         /**
58          * Common private constructor that sets the values and sets up the borders.
59          * Either model or group must be null.
60          * 
61          * @param model
62          * @param showValue
63          * @param group
64          * @param actions
65          */
66         private UnitSelector(DoubleModel model, boolean showValue, UnitGroup group,
67                         Action[] actions) {
68                 super();
69
70                 this.model = model;
71                 this.showValue = showValue;
72
73                 if (model != null) {
74                         this.unitGroup = model.getUnitGroup();
75                         this.currentUnit = model.getCurrentUnit();
76                 } else if (group != null) {
77                         this.unitGroup = group;
78                         this.currentUnit = group.getDefaultUnit();
79                 } else {
80                         this.unitGroup = UnitGroup.UNITS_NONE;
81                         this.currentUnit = UnitGroup.UNITS_NONE.getDefaultUnit();
82                 }
83
84                 this.extraActions = actions;
85
86                 addMouseListener(this);
87
88                 // Define borders to use:
89
90                 normalBorder = new CompoundBorder(
91                                 new LineBorder(new Color(0f, 0f, 0f, 0.08f), 1), new EmptyBorder(1, 1, 1,
92                                                 1));
93                 withinBorder = new CompoundBorder(new LineBorder(new Color(0f, 0f, 0f, 0.6f)),
94                                 new EmptyBorder(1, 1, 1, 1));
95
96                 // Add model listener if showing value
97                 if (showValue)
98                         this.model.addChangeListener(this);
99                 
100                 setBorder(normalBorder);
101                 updateText();
102         }
103
104
105
106         public UnitSelector(DoubleModel model, Action... actions) {
107                 this(model, false, actions);
108         }
109
110         public UnitSelector(DoubleModel model, boolean showValue, Action... actions) {
111                 this(model, showValue, null, actions);
112         }
113
114
115         public UnitSelector(UnitGroup group, Action... actions) {
116                 this(null, false, group, actions);
117         }
118
119
120
121
122         /**
123          * Return the DoubleModel that is backing this selector up, or <code>null</code>.
124          * Either this method or {@link #getUnitGroup()} always returns <code>null</code>.
125          * 
126          * @return              the DoubleModel being used, or <code>null</code>.
127          */
128         public DoubleModel getModel() {
129                 return model;
130         }
131
132         
133         /**
134          * Set the current double model.  
135          * 
136          * @param model         the model to set, <code>null</code> is NOT allowed.
137          */
138         public void setModel(DoubleModel model) {
139                 if (this.model != null && showValue) {
140                         this.model.removeChangeListener(this);
141                 }
142                 this.model = model;
143                 this.unitGroup = model.getUnitGroup();
144                 this.currentUnit = model.getCurrentUnit();
145                 if (showValue) {
146                         this.model.addChangeListener(this);
147                 }
148                 updateText();
149         }
150         
151         
152
153         /**
154          * Return the unit group that is being shown, or <code>null</code>.  Either this method
155          * or {@link #getModel()} always returns <code>null</code>.
156          * 
157          * @return              the UnitGroup being used, or <code>null</code>.
158          */
159         public UnitGroup getUnitGroup() {
160                 return unitGroup;
161         }
162
163
164         public void setUnitGroup(UnitGroup group) {
165                 if (model != null) {
166                         throw new IllegalStateException(
167                                         "UnitGroup cannot be set when backed up with model.");
168                 }
169
170                 if (this.unitGroup == group)
171                         return;
172
173                 this.unitGroup = group;
174                 this.currentUnit = group.getDefaultUnit();
175                 updateText();
176         }
177
178
179         /**
180          * Return the currently selected unit.  Works both when backup up with a DoubleModel
181          * and UnitGroup.
182          * 
183          * @return              the currently selected unit.
184          */
185         public Unit getSelectedUnit() {
186                 return currentUnit;
187         }
188
189
190         /**
191          * Set the currently selected unit.  Sets it to the DoubleModel if it is backed up
192          * by it.
193          * 
194          * @param unit          the unit to select.
195          */
196         public void setSelectedUnit(Unit unit) {
197                 if (!unitGroup.contains(unit)) {
198                         throw new IllegalArgumentException("unit " + unit
199                                         + " not contained in group " + unitGroup);
200                 }
201
202                 this.currentUnit = unit;
203                 if (model != null) {
204                         model.setCurrentUnit(unit);
205                 }
206                 updateText();
207                 fireItemEvent();
208         }
209
210
211
212         /**
213          * Updates the text of the label
214          */
215         private void updateText() {
216                 if (model != null) {
217
218                         Unit unit = model.getCurrentUnit();
219                         if (showValue) {
220                                 setText(unit.toStringUnit(model.getValue()));
221                         } else {
222                                 setText(unit.getUnit());
223                         }
224
225                 } else if (unitGroup != null) {
226
227                         setText(currentUnit.getUnit());
228
229                 } else {
230                         throw new IllegalStateException("Both model and unitGroup are null.");
231                 }
232         }
233
234
235         /**
236          * Update the component when the DoubleModel changes.
237          */
238         @Override
239         public void stateChanged(EventObject e) {
240                 updateText();
241         }
242
243
244
245         ////////  ItemListener handling  ////////
246
247         public void addItemListener(ItemListener listener) {
248                 itemListeners.add(listener);
249         }
250
251         public void removeItemListener(ItemListener listener) {
252                 itemListeners.remove(listener);
253         }
254
255         protected void fireItemEvent() {
256                 ItemEvent event = null;
257                 ItemListener[] listeners = itemListeners.toArray(new ItemListener[0]);
258                 for (ItemListener l: listeners) {
259                         if (event == null) {
260                                 event = new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, getSelectedUnit(),
261                                                 ItemEvent.SELECTED);
262                         }
263                         l.itemStateChanged(event);
264                 }
265         }
266
267
268
269         ////////  Popup  ////////
270
271         private void popup() {
272                 JPopupMenu popup = new JPopupMenu();
273
274                 for (int i = 0; i < unitGroup.getUnitCount(); i++) {
275                         Unit unit = unitGroup.getUnit(i);
276                         JMenuItem item = new JMenuItem(unit.getUnit());
277                         item.addActionListener(new UnitSelectorItem(unit));
278                         popup.add(item);
279                 }
280
281                 for (int i = 0; i < extraActions.length; i++) {
282                         if (extraActions[i] == null && i < extraActions.length - 1) {
283                                 popup.addSeparator();
284                         } else {
285                                 popup.add(new JMenuItem(extraActions[i]));
286                         }
287                 }
288
289                 Dimension d = getSize();
290                 popup.show(this, 0, d.height);
291         }
292
293
294         /**
295          * ActionListener class that sets the currently selected unit.
296          */
297         private class UnitSelectorItem implements ActionListener {
298                 private final Unit unit;
299
300                 public UnitSelectorItem(Unit u) {
301                         unit = u;
302                 }
303
304                 public void actionPerformed(ActionEvent e) {
305                         setSelectedUnit(unit);
306                 }
307         }
308
309
310         @Override
311         public Object[] getSelectedObjects() {
312                 return new Object[]{ getSelectedUnit() };
313         }
314
315
316
317         ////////  Mouse handling ////////
318
319         public void mouseClicked(MouseEvent e) {
320                 if (unitGroup.getUnitCount() > 1)
321                         popup();
322         }
323
324         public void mouseEntered(MouseEvent e) {
325                 if (unitGroup.getUnitCount() > 1)
326                         setBorder(withinBorder);
327         }
328
329         public void mouseExited(MouseEvent e) {
330                 setBorder(normalBorder);
331         }
332
333         public void mousePressed(MouseEvent e) {
334         } // Ignore
335
336         public void mouseReleased(MouseEvent e) {
337         } // Ignore
338
339 }