001 package org.LiveGraph.gui;
002
003 import java.awt.Color;
004 import java.awt.Point;
005 import java.awt.event.FocusEvent;
006 import java.awt.event.FocusListener;
007 import java.awt.event.KeyEvent;
008 import java.awt.event.KeyListener;
009 import java.util.HashMap;
010
011 import javax.swing.JLabel;
012 import javax.swing.JTextField;
013 import javax.swing.Popup;
014 import javax.swing.PopupFactory;
015
016 import com.softnetConsult.utils.string.StringTools;
017 import com.softnetConsult.utils.sys.SystemTools;
018
019
020 /**
021 * This validating adaptor listens to {@code focusLost}-messages of a
022 * {@code JTextField}. If at that moment the field contains a valid
023 * {@code double} value (as a string), the {@link #valueChanged(JTextField, double)}-method is
024 * called. That method must be overridden by subclasses to take some appropriate
025 * action. If, however, the field does not contain a valid {@code double} value,
026 * the {@link #valueChanged(JTextField, double)}-method is not called and a tooltip with an error
027 * message is displayed near the text fiels. The last known "good" value is then
028 * restored.
029 *
030 * <p style="font-size:smaller;">This product includes software developed by the
031 * <strong>LiveGraph</strong> project and its contributors.<br />
032 * (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>)<br />
033 * Copyright (c) 2007-2008 G. Paperin.<br />
034 * All rights reserved.
035 * </p>
036 * <p style="font-size:smaller;">File: RealNumFieldValueChangeAdaptor.java</p>
037 * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
038 * without modification, are permitted provided that the following terms and conditions are met:
039 * </p>
040 * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
041 * acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
042 * this list of conditions and the following disclaimer.<br />
043 * 2. Redistributions in binary form must reproduce the above acknowledgement of the
044 * LiveGraph project and its web-site, the above copyright notice, this list of conditions
045 * and the following disclaimer in the documentation and/or other materials provided with
046 * the distribution.<br />
047 * 3. All advertising materials mentioning features or use of this software or any derived
048 * software must display the following acknowledgement:<br />
049 * <em>This product includes software developed by the LiveGraph project and its
050 * contributors.<br />(http://www.live-graph.org)</em><br />
051 * 4. All advertising materials distributed in form of HTML pages or any other technology
052 * permitting active hyper-links that mention features or use of this software or any
053 * derived software must display the acknowledgment specified in condition 3 of this
054 * agreement, and in addition, include a visible and working hyper-link to the LiveGraph
055 * homepage (http://www.live-graph.org).
056 * </p>
057 * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY
058 * OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
059 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
060 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
061 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
062 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
063 * </p>
064 *
065 * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
066 * @version {@value org.LiveGraph.LiveGraph#version}
067 */
068 public abstract class RealNumFieldValueChangeAdaptor implements FocusListener, KeyListener {
069
070 /**
071 * Display length for the error messgae tooltip in milliseconds.
072 */
073 public static final long TOOLTIP_DISPLAY_LEN = 2000;
074
075 private double defaultValue = Double.NaN;
076 private HashMap<JTextField, Double> lastLegalvaluesCache = null;
077
078 /**
079 * Constructor.
080 * @param defaultValue Default "good" value.
081 */
082 public RealNumFieldValueChangeAdaptor(double defaultValue) {
083 this.defaultValue = defaultValue;
084 this.lastLegalvaluesCache = new HashMap<JTextField, Double>();
085 }
086
087 /**
088 * Does nothing.
089 */
090 public void focusGained(FocusEvent e) {
091 ;
092 }
093
094 /**
095 * Catches the focus lost event and performs field validation.
096 */
097 public void focusLost(FocusEvent e) {
098 Object source = e.getSource();
099 if (source instanceof JTextField)
100 handleEvent((JTextField) source);
101 }
102
103 /**
104 * Catches the enter pressed event and performs field validation.
105 */
106 public void keyPressed(KeyEvent e) {
107 Object source = e.getSource();
108 if (! (source instanceof JTextField))
109 return;
110 if (KeyEvent.VK_ENTER != e.getKeyCode())
111 return;
112 handleEvent((JTextField) source);
113 }
114
115 /**
116 * Does nothing.
117 */
118 public void keyReleased(KeyEvent e) {
119 ;
120 }
121
122 /**
123 * Does nothing.
124 */
125 public void keyTyped(KeyEvent e) {
126 ;
127 }
128
129 /**
130 * Performs the validation and handles appropriately.
131 *
132 * @param field The text field that generated the event.
133 */
134 public void handleEvent(final JTextField field) {
135 try {
136 double val = StringTools.parseDouble(field.getText());
137 lastLegalvaluesCache.put(field, val);
138 double newVal = valueChanged(field, val);
139 if (newVal != val) {
140 field.setText(StringTools.toString(newVal));
141 lastLegalvaluesCache.put(field, newVal);
142 }
143 } catch (NumberFormatException ex) {
144 Point sc = field.getLocationOnScreen();
145 sc.x += field.getWidth() / 2;
146 sc.y += field.getHeight() / 2;
147 JLabel info = new JLabel("You need a valid real value here. \"" + field.getText() + "\" is not a real number.");
148 info.setOpaque(true);
149 info.setBackground(Color.ORANGE);
150 info.setForeground(Color.DARK_GRAY);
151 final Popup popup = PopupFactory.getSharedInstance().getPopup(field, info, sc.x, sc.y);
152 popup.show();
153 Thread offSwitch = new Thread(new Runnable() {
154 public void run() {
155 try {
156 SystemTools.sleep(TOOLTIP_DISPLAY_LEN);
157 } finally {
158 popup.hide();
159 if (lastLegalvaluesCache.containsKey(field))
160 field.setText(lastLegalvaluesCache.get(field).toString());
161 else
162 field.setText(StringTools.toString(defaultValue));
163 }
164 }}, "RealNumFieldValueChangeAdaptor.OffSwitch");
165 offSwitch.start();
166 }
167 }
168
169 /**
170 * Subclasses must override that in order to take the appropriate action when the field
171 * contains a valid {@code double} value.
172 *
173 * @param field The text field containing the value.
174 * @param newValue The {@code double} value in the field.
175 * @return A string representing the value returned by this method will be used instead
176 * of the current field content.
177 */
178 abstract public double valueChanged(JTextField field, double newValue);
179
180 }