001 /*
002 * $Id: McVTextField.java,v 1.13 2012/02/19 17:35:52 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2012
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
031 package edu.wisc.ssec.mcidasv.util;
032
033 import java.awt.BorderLayout;
034 import java.awt.Color;
035 import java.awt.event.FocusEvent;
036 import java.awt.event.FocusListener;
037 import java.util.regex.Matcher;
038 import java.util.regex.Pattern;
039 import java.util.regex.PatternSyntaxException;
040
041 import javax.swing.InputVerifier;
042 import javax.swing.JComponent;
043 import javax.swing.JLabel;
044 import javax.swing.JTextField;
045 import javax.swing.SwingConstants;
046 import javax.swing.border.EmptyBorder;
047 import javax.swing.event.DocumentEvent;
048 import javax.swing.event.DocumentListener;
049 import javax.swing.text.AttributeSet;
050 import javax.swing.text.BadLocationException;
051 import javax.swing.text.Document;
052 import javax.swing.text.JTextComponent;
053 import javax.swing.text.PlainDocument;
054
055 /**
056 * Extend JTextField to add niceties such as uppercase,
057 * length limits, and allow/deny character sets
058 */
059 public class McVTextField extends JTextField {
060
061 public static char[] mcidasDeny = new char[] { '/', '.', ' ', '[', ']', '%' };
062
063 public static Pattern ipAddress = Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
064
065 private McVTextFieldDocument document = new McVTextFieldDocument();
066
067 private Pattern validPattern;
068 private String[] validStrings;
069
070 public McVTextField() {
071 this("", 0, false);
072 }
073
074 public McVTextField(String defaultString) {
075 this(defaultString, 0, false);
076 }
077
078 public McVTextField(String defaultString, int limit) {
079 this(defaultString, limit, false);
080 }
081
082 public McVTextField(String defaultString, boolean upper) {
083 this(defaultString, 0, upper);
084 }
085
086 // All other constructors call this one
087 public McVTextField(String defaultString, int limit, boolean upper) {
088 super(limit);
089 this.document = new McVTextFieldDocument(limit, upper);
090 super.setDocument(document);
091 this.setText(defaultString);
092 }
093
094 public McVTextField(String defaultString, int limit, boolean upper, String allow, String deny) {
095 this(defaultString, limit, upper);
096 setAllow(makePattern(allow));
097 setDeny(makePattern(deny));
098 }
099
100 public McVTextField(String defaultString, int limit, boolean upper, char[] allow, char[] deny) {
101 this(defaultString, limit, upper);
102 setAllow(makePattern(allow));
103 setDeny(makePattern(deny));
104 }
105
106 public McVTextField(String defaultString, int limit, boolean upper, Pattern allow, Pattern deny) {
107 this(defaultString, limit, upper);
108 setAllow(allow);
109 setDeny(deny);
110 }
111
112 public int getLimit() {
113 return this.document.getLimit();
114 }
115
116 public void setLimit(int limit) {
117 this.document.setLimit(limit);
118 super.setDocument(document);
119 }
120
121 public boolean getUppercase() {
122 return this.document.getUppercase();
123 }
124
125 public void setUppercase(boolean uppercase) {
126 this.document.setUppercase(uppercase);
127 super.setDocument(document);
128 }
129
130 public void setAllow(String string) {
131 this.document.setAllow(makePattern(string));
132 super.setDocument(document);
133 }
134
135 public void setDeny(String string) {
136 this.document.setDeny(makePattern(string));
137 super.setDocument(document);
138 }
139
140 public void setAllow(char[] characters) {
141 this.document.setAllow(makePattern(characters));
142 super.setDocument(document);
143 }
144
145 public void setDeny(char[] characters) {
146 this.document.setDeny(makePattern(characters));
147 super.setDocument(document);
148 }
149
150 public void setAllow(Pattern newPattern) {
151 this.document.setAllow(newPattern);
152 super.setDocument(document);
153 }
154
155 public void setDeny(Pattern newPattern) {
156 this.document.setDeny(newPattern);
157 super.setDocument(document);
158 }
159
160 // Take a string and turn it into a pattern
161 private Pattern makePattern(String string) {
162 if (string == null) return null;
163 try {
164 return Pattern.compile(string);
165 }
166 catch (PatternSyntaxException e) {
167 return null;
168 }
169 }
170
171 // Take a character array and turn it into a [abc] class pattern
172 private Pattern makePattern(char[] characters) {
173 if (characters == null) return null;
174 String string = ".*";
175 if (characters.length > 0) {
176 string = "[";
177 for (char c : characters) {
178 if (c == '[') string += "\\[";
179 else if (c == ']') string += "\\]";
180 else if (c == '\\') string += "\\\\";
181 else string += c;
182 }
183 string += "]";
184 }
185 try {
186 return Pattern.compile(string);
187 }
188 catch (PatternSyntaxException e) {
189 return null;
190 }
191 }
192
193 // Add an InputVerifier if we want to validate a particular pattern
194 public void setValidPattern(String string) {
195 if (string == null) return;
196 try {
197 Pattern newPattern = Pattern.compile(string);
198 setValidPattern(newPattern);
199 }
200 catch (PatternSyntaxException e) {
201 return;
202 }
203 }
204
205 // Add an InputVerifier if we want to validate a particular pattern
206 public void setValidPattern(Pattern pattern) {
207 if (pattern == null) {
208 this.validPattern = null;
209 if (this.validStrings == null) removeInputVerifier();
210 }
211 else {
212 this.validPattern = pattern;
213 addInputVerifier();
214 }
215 }
216
217 // Add an InputVerifier if we want to validate a particular set of strings
218 public void setValidStrings(String[] strings) {
219 if (strings == null) {
220 this.validStrings = null;
221 if (this.validPattern == null) removeInputVerifier();
222 }
223 else {
224 this.validStrings = strings;
225 addInputVerifier();
226 }
227 }
228
229 private void addInputVerifier() {
230 this.setInputVerifier(new InputVerifier() {
231 public boolean verify(JComponent comp) {
232 return verifyInput();
233 }
234 public boolean shouldYieldFocus(JComponent comp) {
235 boolean valid = verify(comp);
236 if (!valid) {
237 getToolkit().beep();
238 }
239 return valid;
240 }
241 });
242 verifyInput();
243 }
244
245 private void removeInputVerifier() {
246 this.setInputVerifier(null);
247 }
248
249 private boolean verifyInput() {
250 boolean isValid = false;
251 String checkValue = this.getText();
252 if (checkValue=="") return true;
253
254 if (this.validStrings != null) {
255 for (String string : validStrings) {
256 if (checkValue.equals(string)) isValid = true;
257 }
258 }
259
260 if (this.validPattern != null) {
261 Matcher validMatch = this.validPattern.matcher(checkValue);
262 isValid = isValid || validMatch.matches();
263 }
264
265 if (!isValid) {
266 this.selectAll();
267 }
268 return isValid;
269 }
270
271 /**
272 * Extend PlainDocument to get the character validation features we require
273 */
274 private class McVTextFieldDocument extends PlainDocument {
275 private int limit;
276 private boolean toUppercase = false;
277 private boolean hasPatterns = false;
278 private Pattern allow = Pattern.compile(".*");
279 private Pattern deny = null;
280
281 public McVTextFieldDocument() {
282 super();
283 }
284
285 public McVTextFieldDocument(int limit, boolean upper) {
286 super();
287 setLimit(limit);
288 setUppercase(upper);
289 }
290
291 public void insertString(int offset, String str, AttributeSet attr) throws BadLocationException {
292 if (str == null) return;
293 if (toUppercase) str = str.toUpperCase();
294
295 // Only allow certain patterns, and only check if we think we have patterns
296 if (hasPatterns) {
297 char[] characters = str.toCharArray();
298 String okString = "";
299 for (char c : characters) {
300 String s = "" + c;
301 if (deny != null) {
302 Matcher denyMatch = deny.matcher(s);
303 if (denyMatch.matches()) continue;
304 }
305 if (allow != null) {
306 Matcher allowMatch = allow.matcher(s);
307 if (allowMatch.matches()) okString += s;
308 }
309 }
310 str = okString;
311 }
312 if (str.equals("")) return;
313
314 if ((getLength() + str.length()) <= limit || limit <= 0) {
315 super.insertString(offset, str, attr);
316 }
317 }
318
319 public int getLimit() {
320 return this.limit;
321 }
322
323 public void setLimit(int limit) {
324 this.limit = limit;
325 }
326
327 public boolean getUppercase() {
328 return this.toUppercase;
329 }
330
331 public void setUppercase(boolean uppercase) {
332 this.toUppercase = uppercase;
333 }
334
335 public void setAllow(Pattern newPattern) {
336 if (newPattern==null) return;
337 this.allow = newPattern;
338 hasPatterns = true;
339 }
340
341 public void setDeny(Pattern newPattern) {
342 if (newPattern==null) return;
343 this.deny = newPattern;
344 hasPatterns = true;
345 }
346
347 }
348
349 public static class Prompt extends JLabel implements FocusListener, DocumentListener {
350
351 public enum FocusBehavior { ALWAYS, FOCUS_GAINED, FOCUS_LOST };
352
353 private final JTextComponent component;
354
355 private final Document document;
356
357 private FocusBehavior focus;
358
359 private boolean showPromptOnce;
360
361 private int focusLost;
362
363 public Prompt(final JTextComponent component, final String text) {
364 this(component, FocusBehavior.FOCUS_LOST, text);
365 }
366
367 public Prompt(final JTextComponent component, final FocusBehavior focusBehavior, final String text) {
368 this.component = component;
369 setFocusBehavior(focusBehavior);
370
371 document = component.getDocument();
372
373 setText(text);
374 setFont(component.getFont());
375 setForeground(component.getForeground());
376 setHorizontalAlignment(JLabel.LEADING);
377 setEnabled(false);
378
379 component.addFocusListener(this);
380 document.addDocumentListener(this);
381
382 component.setLayout(new BorderLayout());
383 component.add(this);
384 checkForPrompt();
385 }
386
387 public FocusBehavior getFocusBehavior() {
388 return focus;
389 }
390
391 public void setFocusBehavior(final FocusBehavior focus) {
392 this.focus = focus;
393 }
394
395 public boolean getShowPromptOnce() {
396 return showPromptOnce;
397 }
398
399 public void setShowPromptOnce(final boolean showPromptOnce) {
400 this.showPromptOnce = showPromptOnce;
401 }
402
403 /**
404 * Check whether the prompt should be visible or not. The visibility
405 * will change on updates to the Document and on focus changes.
406 */
407 private void checkForPrompt() {
408 // text has been entered, remove the prompt
409 if (document.getLength() > 0) {
410 setVisible(false);
411 return;
412 }
413
414 // prompt has already been shown once, remove it
415 if (showPromptOnce && focusLost > 0) {
416 setVisible(false);
417 return;
418 }
419
420 // check the behavior property and component focus to determine if the
421 // prompt should be displayed.
422 if (component.hasFocus()) {
423 if (focus == FocusBehavior.ALWAYS || focus == FocusBehavior.FOCUS_GAINED) {
424 setVisible(true);
425 } else {
426 setVisible(false);
427 }
428 } else {
429 if (focus == FocusBehavior.ALWAYS || focus == FocusBehavior.FOCUS_LOST) {
430 setVisible( true);
431 } else {
432 setVisible(false);
433 }
434 }
435 }
436
437 // from FocusListener
438 public void focusGained(FocusEvent e) {
439 checkForPrompt();
440 }
441
442 public void focusLost(FocusEvent e) {
443 focusLost++;
444 checkForPrompt();
445 }
446
447 // from DocumentListener
448 public void insertUpdate(DocumentEvent e) {
449 checkForPrompt();
450 }
451
452 public void removeUpdate(DocumentEvent e) {
453 checkForPrompt();
454 }
455
456 public void changedUpdate(DocumentEvent e) {}
457 }
458 }