001/*
002 * $Id: JSpinField.java,v 1.2 2011/03/24 16:06:33 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 * 
012 * All Rights Reserved
013 * 
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.  
016 * 
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 * 
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025 * GNU Lesser Public License for more details.
026 * 
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program.  If not, see http://www.gnu.org/licenses.
029 */
030
031package edu.wisc.ssec.mcidasv.data.dateChooser;
032
033import java.awt.BorderLayout;
034import java.awt.Color;
035import java.awt.Component;
036import java.awt.Dimension;
037import java.awt.Font;
038import java.awt.event.ActionEvent;
039import java.awt.event.ActionListener;
040import java.awt.event.FocusEvent;
041import java.awt.event.FocusListener;
042
043import javax.swing.BorderFactory;
044import javax.swing.JFrame;
045import javax.swing.JPanel;
046import javax.swing.JSpinner;
047import javax.swing.JTextField;
048import javax.swing.SpinnerNumberModel;
049import javax.swing.SwingConstants;
050import javax.swing.UIManager;
051import javax.swing.event.CaretEvent;
052import javax.swing.event.CaretListener;
053import javax.swing.event.ChangeEvent;
054import javax.swing.event.ChangeListener;
055
056/**
057 * JSpinField is a numeric field with 2 spin buttons to increase or decrease the
058 * value. It has the same interface as the "old" JSpinField but uses a JSpinner
059 * internally (since J2SE SDK 1.4) rather than a scrollbar for emulating the
060 * spin buttons.
061 * 
062 * @author Kai Toedter
063 * @version $LastChangedRevision: 85 $
064 * @version $LastChangedDate: 2006-04-28 13:50:52 +0200 (Fr, 28 Apr 2006) $
065 */
066public class JSpinField extends JPanel implements ChangeListener, CaretListener, ActionListener,
067                FocusListener {
068        private static final long serialVersionUID = 1694904792717740650L;
069
070        protected JSpinner spinner;
071
072        /** the text (number) field */
073        protected JTextField textField;
074        protected int min;
075        protected int max;
076        protected int value;
077        protected Color darkGreen;
078
079        /**
080         * Default JSpinField constructor. The valid value range is between
081         * Integer.MIN_VALUE and Integer.MAX_VALUE. The initial value is 0.
082         */
083        public JSpinField() {
084                this(Integer.MIN_VALUE, Integer.MAX_VALUE);
085        }
086
087        /**
088         * JSpinField constructor with given minimum and maximum vaues and initial
089         * value 0.
090         */
091        public JSpinField(int min, int max) {
092                super();
093                setName("JSpinField");
094                this.min = min;
095                if (max < min)
096                        max = min;
097                this.max = max;
098                value = 0;
099                if (value < min)
100                        value = min;
101                if (value > max)
102                        value = max;
103
104                darkGreen = new Color(0, 150, 0);
105                setLayout(new BorderLayout());
106                textField = new JTextField();
107                textField.addCaretListener(this);
108                textField.addActionListener(this);
109                textField.setHorizontalAlignment(SwingConstants.RIGHT);
110                textField.setBorder(BorderFactory.createEmptyBorder());
111                textField.setText(Integer.toString(value));
112                textField.addFocusListener(this);
113                spinner = new JSpinner() {
114                        private static final long serialVersionUID = -6287709243342021172L;
115                        private JTextField textField = new JTextField();
116
117                        public Dimension getPreferredSize() {
118                                Dimension size = super.getPreferredSize();
119                                return new Dimension(size.width, textField.getPreferredSize().height);
120                        }
121                };
122                spinner.setEditor(textField);
123                spinner.addChangeListener(this);
124                // spinner.setSize(spinner.getWidth(), textField.getHeight());
125                add(spinner, BorderLayout.CENTER);
126        }
127
128        public void adjustWidthToMaximumValue() {
129                JTextField testTextField = new JTextField(Integer.toString(max));
130                int width = testTextField.getPreferredSize().width;
131                int height = testTextField.getPreferredSize().height;
132                textField.setPreferredSize(new Dimension(width, height));
133                textField.revalidate();
134        }
135
136        /**
137         * Is invoked when the spinner model changes
138         * 
139         * @param e
140         *            the ChangeEvent
141         */
142        public void stateChanged(ChangeEvent e) {
143                SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel();
144                int value = model.getNumber().intValue();
145                setValue(value);
146        }
147
148        /**
149         * Sets the value attribute of the JSpinField object.
150         * 
151         * @param newValue
152         *            The new value
153         * @param updateTextField
154         *            true if text field should be updated
155         */
156        protected void setValue(int newValue, boolean updateTextField, boolean firePropertyChange) {
157                int oldValue = value;
158                if (newValue < min) {
159                        value = min;
160                } else if (newValue > max) {
161                        value = max;
162                } else {
163                        value = newValue;
164                }
165
166                if (updateTextField) {
167                        textField.setText(Integer.toString(value));
168                        textField.setForeground(Color.black);
169                }
170
171                if (firePropertyChange) {
172                        firePropertyChange("value", oldValue, value);
173                }
174        }
175
176        /**
177         * Sets the value. This is a bound property.
178         * 
179         * @param newValue
180         *            the new value
181         * 
182         * @see #getValue
183         */
184        public void setValue(int newValue) {
185                setValue(newValue, true, true);
186                spinner.setValue(new Integer(value));
187        }
188
189        /**
190         * Returns the value.
191         * 
192         * @return the value value
193         */
194        public int getValue() {
195                return value;
196        }
197
198        /**
199         * Sets the minimum value.
200         * 
201         * @param newMinimum
202         *            the new minimum value
203         * 
204         * @see #getMinimum
205         */
206        public void setMinimum(int newMinimum) {
207                min = newMinimum;
208        }
209
210        /**
211         * Returns the minimum value.
212         * 
213         * @return the minimum value
214         */
215        public int getMinimum() {
216                return min;
217        }
218
219        /**
220         * Sets the maximum value and adjusts the preferred width.
221         * 
222         * @param newMaximum
223         *            the new maximum value
224         * 
225         * @see #getMaximum
226         */
227        public void setMaximum(int newMaximum) {
228                max = newMaximum;
229        }
230
231        /**
232         * Sets the horizontal alignment of the displayed value.
233         * 
234         * @param alignment
235         *            the horizontal alignment
236         */
237        public void setHorizontalAlignment(int alignment) {
238                textField.setHorizontalAlignment(alignment);
239        }
240
241        /**
242         * Returns the maximum value.
243         * 
244         * @return the maximum value
245         */
246        public int getMaximum() {
247                return max;
248        }
249
250        /**
251         * Sets the font property.
252         * 
253         * @param font
254         *            the new font
255         */
256        public void setFont(Font font) {
257                if (textField != null) {
258                        textField.setFont(font);
259                }
260        }
261
262        /**
263         * Sets the foreground
264         * 
265         * @param fg
266         *            the foreground
267         */
268        public void setForeground(Color fg) {
269                if (textField != null) {
270                        textField.setForeground(fg);
271                }
272        }
273
274        /**
275         * After any user input, the value of the textfield is proofed. Depending on
276         * being an integer, the value is colored green or red.
277         * 
278         * @param e
279         *            the caret event
280         */
281        public void caretUpdate(CaretEvent e) {
282                try {
283                        int testValue = Integer.valueOf(textField.getText()).intValue();
284
285                        if ((testValue >= min) && (testValue <= max)) {
286                                textField.setForeground(darkGreen);
287                                setValue(testValue, false, true);
288                        } else {
289                                textField.setForeground(Color.red);
290                        }
291                } catch (Exception ex) {
292                        if (ex instanceof NumberFormatException) {
293                                textField.setForeground(Color.red);
294                        }
295
296                        // Ignore all other exceptions, e.g. illegal state exception
297                }
298
299                textField.repaint();
300        }
301
302        /**
303         * After any user input, the value of the textfield is proofed. Depending on
304         * being an integer, the value is colored green or red. If the textfield is
305         * green, the enter key is accepted and the new value is set.
306         * 
307         * @param e
308         *            Description of the Parameter
309         */
310        public void actionPerformed(ActionEvent e) {
311                if (textField.getForeground().equals(darkGreen)) {
312                        setValue(Integer.valueOf(textField.getText()).intValue());
313                }
314        }
315
316        /**
317         * Enable or disable the JSpinField.
318         * 
319         * @param enabled
320         *            The new enabled value
321         */
322        public void setEnabled(boolean enabled) {
323                super.setEnabled(enabled);
324                spinner.setEnabled(enabled);
325                textField.setEnabled(enabled);
326                /*
327                 * Fixes the background bug
328                 * 4991597 and sets the background explicitely to a
329                 * TextField.inactiveBackground.
330                 */
331                if (!enabled) {
332                        textField.setBackground(UIManager.getColor("TextField.inactiveBackground"));
333                }
334        }
335
336        /**
337         * Returns the year chooser's spinner (which allow the focus to be set to
338         * it).
339         * 
340         * @return Component the spinner or null, if the month chooser has no
341         *         spinner
342         */
343        public Component getSpinner() {
344                return spinner;
345        }
346
347        /**
348         * Creates a JFrame with a JSpinField inside and can be used for testing.
349         * 
350         * @param s
351         *            The command line arguments
352         */
353        public static void main(String[] s) {
354                JFrame frame = new JFrame("JSpinField");
355                frame.getContentPane().add(new JSpinField());
356                frame.pack();
357                frame.setVisible(true);
358        }
359
360        /*
361         * (non-Javadoc)
362         * 
363         * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
364         */
365        public void focusGained(FocusEvent e) {
366        }
367
368        /**
369         * The value of the text field is checked against a valid (green) value. If
370         * valid, the value is set and a property change is fired.
371         */
372        public void focusLost(FocusEvent e) {
373                actionPerformed(null);
374        }
375}