updates for 0.9.4
[debian/openrocket] / 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.List;
15
16 import javax.swing.Action;
17 import javax.swing.JMenuItem;
18 import javax.swing.JPopupMenu;
19 import javax.swing.border.Border;
20 import javax.swing.border.CompoundBorder;
21 import javax.swing.border.EmptyBorder;
22 import javax.swing.border.LineBorder;
23 import javax.swing.event.ChangeEvent;
24 import javax.swing.event.ChangeListener;
25
26 import net.sf.openrocket.gui.adaptors.DoubleModel;
27 import net.sf.openrocket.unit.Unit;
28 import net.sf.openrocket.unit.UnitGroup;
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 ResizeLabel implements ChangeListener, 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         public void stateChanged(ChangeEvent e) {
239                 updateText();
240         }
241
242
243
244         ////////  ItemListener handling  ////////
245
246         public void addItemListener(ItemListener listener) {
247                 itemListeners.add(listener);
248         }
249
250         public void removeItemListener(ItemListener listener) {
251                 itemListeners.remove(listener);
252         }
253
254         protected void fireItemEvent() {
255                 ItemEvent event = null;
256                 ItemListener[] listeners = itemListeners.toArray(new ItemListener[0]);
257                 for (ItemListener l: listeners) {
258                         if (event == null) {
259                                 event = new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, getSelectedUnit(),
260                                                 ItemEvent.SELECTED);
261                         }
262                         l.itemStateChanged(event);
263                 }
264         }
265
266
267
268         ////////  Popup  ////////
269
270         private void popup() {
271                 JPopupMenu popup = new JPopupMenu();
272
273                 for (int i = 0; i < unitGroup.getUnitCount(); i++) {
274                         Unit unit = unitGroup.getUnit(i);
275                         JMenuItem item = new JMenuItem(unit.getUnit());
276                         item.addActionListener(new UnitSelectorItem(unit));
277                         popup.add(item);
278                 }
279
280                 for (int i = 0; i < extraActions.length; i++) {
281                         if (extraActions[i] == null && i < extraActions.length - 1) {
282                                 popup.addSeparator();
283                         } else {
284                                 popup.add(new JMenuItem(extraActions[i]));
285                         }
286                 }
287
288                 Dimension d = getSize();
289                 popup.show(this, 0, d.height);
290         }
291
292
293         /**
294          * ActionListener class that sets the currently selected unit.
295          */
296         private class UnitSelectorItem implements ActionListener {
297                 private final Unit unit;
298
299                 public UnitSelectorItem(Unit u) {
300                         unit = u;
301                 }
302
303                 public void actionPerformed(ActionEvent e) {
304                         setSelectedUnit(unit);
305                 }
306         }
307
308
309         @Override
310         public Object[] getSelectedObjects() {
311                 return new Object[]{ getSelectedUnit() };
312         }
313
314
315
316         ////////  Mouse handling ////////
317
318         public void mouseClicked(MouseEvent e) {
319                 if (unitGroup.getUnitCount() > 1)
320                         popup();
321         }
322
323         public void mouseEntered(MouseEvent e) {
324                 if (unitGroup.getUnitCount() > 1)
325                         setBorder(withinBorder);
326         }
327
328         public void mouseExited(MouseEvent e) {
329                 setBorder(normalBorder);
330         }
331
332         public void mousePressed(MouseEvent e) {
333         } // Ignore
334
335         public void mouseReleased(MouseEvent e) {
336         } // Ignore
337
338 }