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