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