1 /* 2 * The contents of this file is dual-licensed under 2 3 * alternative Open Source/Free licenses: LGPL 2.1 or later and 4 * Apache License 2.0. (starting with JNA version 4.0.0). 5 * 6 * You can freely decide which license you want to apply to 7 * the project. 8 * 9 * You may obtain a copy of the LGPL License at: 10 * 11 * http://www.gnu.org/licenses/licenses.html 12 * 13 * A copy is also included in the downloadable source code package 14 * containing JNA, in file "LGPL2.1". 15 * 16 * You may obtain a copy of the Apache License at: 17 * 18 * http://www.apache.org/licenses/ 19 * 20 * A copy is also included in the downloadable source code package 21 * containing JNA, in file "AL2.0". 22 */ 23 package com.sun.jna.contrib.demo; 24 25 import java.awt.BorderLayout; 26 import java.awt.Color; 27 import java.awt.event.FocusAdapter; 28 import java.awt.event.FocusEvent; 29 import java.util.ArrayList; 30 31 import javax.swing.BorderFactory; 32 import javax.swing.JFrame; 33 import javax.swing.JLabel; 34 import javax.swing.JPanel; 35 import javax.swing.JTextField; 36 import javax.swing.Popup; 37 import javax.swing.UIManager; 38 import javax.swing.border.Border; 39 import javax.swing.event.DocumentEvent; 40 import javax.swing.event.DocumentListener; 41 import javax.swing.text.AttributeSet; 42 import javax.swing.text.BadLocationException; 43 import javax.swing.text.Document; 44 import javax.swing.text.PlainDocument; 45 46 /** 47 * The FilteredTextField class is a JTextField that only allows specified 48 * characters to be entered into it. The allowed characters can be added to 49 * the text field, and entry validation is performed as each character is 50 * typed. In addition, complete string validation is tested against a 51 * configurable regular expression when leaving the field. If the string is 52 * invalid the text field is bordered with a red line, and the user is notified 53 * of the error upon returning to the text field. The text field can also be 54 * configured to accept a limited number of characters. 55 */ 56 @SuppressWarnings("serial") 57 public class FilteredTextField extends JTextField { 58 59 public static final Character[] UPPERCASE_CHARS = {'A', 'B', 'C', 'D', 'E', 60 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 61 'U', 'V', 'W', 'X', 'Y', 'Z'}; 62 public static final Character[] LOWERCASE_CHARS = {'a', 'b', 'c', 'd', 'e', 63 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 64 'u', 'v', 'w', 'x', 'y', 'z'}; 65 public static final Character[] NUMERIC_CHARS = {'1', '2', '3', '4', '5', 66 '6', '7', '8', '9', '0'}; 67 68 private static final Integer ENTRY_BALLOON = 0; 69 private static final Integer VALID_BALLOON = 1; 70 private static final Integer LENGTH_BALLOON = 2; 71 private static final Border RED_BORDER 72 = BorderFactory.createLineBorder(Color.RED, 2); 73 74 private ArrayList<Character> allowable = new ArrayList<Character>(); 75 private int maximumLength = String.valueOf(Long.MAX_VALUE).length(); 76 77 private Border defaultBorder = null; 78 private boolean isValid = true; 79 80 private Popup balloon = null; 81 private String entryError = null; 82 private String validRegex = null; 83 private String validError = null; 84 private Color balloonBorderColor = null; 85 private Color balloonBackgroundColor = null; 86 private Color balloonTextColor = null; 87 private Integer balloonDuration = null; 88 89 private Integer balloonType = null; 90 91 /** 92 * Create a FilteredTextField. 93 */ FilteredTextField()94 public FilteredTextField() { 95 super(); 96 init(); 97 } 98 99 /** 100 * Create a FilteredTextField. 101 * 102 * @param columns the number of columns to use to calculate the preferred 103 * width 104 */ FilteredTextField(int columns)105 public FilteredTextField(int columns) { 106 super(columns); 107 init(); 108 } 109 110 /* 111 * Initialize the FilteredTextField. 112 */ init()113 private void init() { 114 defaultBorder = getBorder(); 115 entryError = ""; 116 validRegex = ""; 117 validError = ""; 118 balloonBorderColor = BalloonTipManager.DEFAULT_BORDER_COLOR; 119 balloonBackgroundColor = BalloonTipManager.DEFAULT_BACKGROUND_COLOR; 120 balloonTextColor = BalloonTipManager.DEFAULT_TEXT_COLOR; 121 balloonDuration = 10000; 122 balloonType = ENTRY_BALLOON; 123 addFocusListener(new ValidationEar()); 124 } 125 126 /** 127 * Sets the allowable character used for entry validation. 128 * 129 * @param characters the allowable characters 130 */ setCharacters(Character[] characters)131 public void setCharacters(Character[] characters) { 132 clearCharacters(); 133 for (int i = 0; i < characters.length; i++) { 134 addCharacter(characters[i]); 135 } 136 } 137 138 /** 139 * Adds the character array to the list used for entry validation. 140 * 141 * @param characters the character array 142 */ addCharacters(Character[] characters)143 public void addCharacters(Character[] characters) { 144 for (int i = 0; i < characters.length; i++) { 145 addCharacter(characters[i]); 146 } 147 } 148 149 /** 150 * Adds the character to the list used for entry validation. 151 * 152 * @param characters the character 153 */ addCharacter(Character character)154 public void addCharacter(Character character) { 155 if (!allowable.contains(character)) { 156 allowable.add(character); 157 } 158 } 159 160 /** 161 * Clears the list of allowable characters for entry validation. 162 */ clearCharacters()163 public void clearCharacters() { 164 allowable.clear(); 165 } 166 167 /** 168 * Removes the character array from the list used for entry validation. 169 * 170 * @param characters the character array 171 */ removeCharacters(Character[] characters)172 public void removeCharacters(Character[] characters) { 173 for (int i = 0; i < characters.length; i++) { 174 removeCharacter(characters[i]); 175 } 176 } 177 178 /** 179 * Removes the character from the list used for entry validation. 180 * 181 * @param character the character 182 */ removeCharacter(Character character)183 public void removeCharacter(Character character) { 184 if (allowable.contains(character)) { 185 allowable.remove(character); 186 } 187 } 188 189 /** 190 * Sets the maximum number of characters for the length of the entry string. 191 * 192 * @param maximumLength the number of characters 193 */ setMaximumLength(int maximumLength)194 public void setMaximumLength(int maximumLength) { 195 this.maximumLength = maximumLength; 196 } 197 198 /** 199 * Sets the message that is displayed when there is an entry error. 200 * 201 * @param entryError the entry error message 202 */ setEntryError(String entryError)203 public void setEntryError(String entryError) { 204 this.entryError = entryError; 205 } 206 207 /** 208 * Sets the regular expression that is used for string validation. String 209 * validation is checked when exiting the text field. 210 * 211 * @param validRegex the validation regular expression 212 */ setValidRegex(String validRegex)213 public void setValidRegex(String validRegex) { 214 this.validRegex = validRegex; 215 } 216 217 /** 218 * Sets the message that is displayed when there is a validation error. 219 * 220 * @param validError the validation error message 221 */ setValidError(String validError)222 public void setValidError(String validError) { 223 this.validError = validError; 224 } 225 226 /** 227 * Sets the color to use for the balloon border. 228 * 229 * @param borderColor the balloon border color 230 */ setBalloonBorderColor(Color borderColor)231 public void setBalloonBorderColor(Color borderColor) { 232 balloonBorderColor = borderColor; 233 } 234 235 /** 236 * Sets the color to use for the balloon background. 237 * 238 * @param backgroundColor the balloon background color 239 */ setBalloonBackgroundColor(Color backgroundColor)240 public void setBalloonBackgroundColor(Color backgroundColor) { 241 balloonBackgroundColor = backgroundColor; 242 } 243 244 /** 245 * Sets the color to use for the balloon text. 246 * 247 * @param textColor the balloon text color 248 */ setBalloonTextColor(Color textColor)249 public void setBalloonTextColor(Color textColor) { 250 balloonTextColor = textColor; 251 } 252 253 /** 254 * Sets the time in milliseconds that the balloon is visible before 255 * disappearing. This is the maximum time that the balloon will be visible, 256 * as other events can also make the balloon disappear. 257 * 258 * @param duration the time in milliseconds 259 */ setBalloonDuration(Integer duration)260 public void setBalloonDuration(Integer duration) { 261 balloonDuration = duration; 262 } 263 264 /* 265 * (non-Javadoc) 266 * @see javax.swing.JTextField#createDefaultModel() 267 */ createDefaultModel()268 protected Document createDefaultModel() { 269 return new FilteredTextFieldDocument(); 270 } 271 272 /* 273 * This class defines the document used for the FilteredTextField. 274 */ 275 private class FilteredTextFieldDocument extends PlainDocument { 276 277 /* 278 * Create a FilteredTextFieldDocument. 279 */ FilteredTextFieldDocument()280 public FilteredTextFieldDocument() { 281 addDocumentListener(new FilteredTextFieldEar()); 282 } 283 284 /* 285 * (non-Javadoc) 286 * @see javax.swing.text.PlainDocument#insertString( 287 * int, java.lang.String, javax.swing.text.AttributeSet) 288 */ insertString(int offset, String str, AttributeSet a)289 public void insertString(int offset, String str, AttributeSet a) 290 throws BadLocationException { 291 if (balloon != null && BalloonTipManager.isShowing()) { 292 if (balloonType == VALID_BALLOON) { 293 balloon.hide(); 294 } 295 } 296 StringBuilder buffer 297 = new StringBuilder(FilteredTextField.this.getText()); 298 if (offset >= 0 && offset <= buffer.length()) { 299 buffer.insert(offset, str); 300 String strBuf = buffer.toString(); 301 302 if (buffer.length() > maximumLength) { 303 if (balloon != null && BalloonTipManager.isShowing()) { 304 if (balloonType == LENGTH_BALLOON) { 305 BalloonTipManager.restartTimer(); 306 return; 307 } else { 308 balloon.hide(); 309 } 310 } 311 balloon = BalloonTipManager.getBalloonTip(FilteredTextField.this, 312 "The number of characters must be less than or equal to " 313 + maximumLength, 0, 0, balloonDuration, balloonBorderColor, 314 balloonBackgroundColor, balloonTextColor); 315 balloon.show(); 316 balloonType = LENGTH_BALLOON; 317 return; 318 } 319 320 if (strBuf == null || strBuf.equals("")) { 321 remove(0, getLength()); 322 super.insertString(0, "", null); 323 if (balloon != null && BalloonTipManager.isShowing()) { 324 balloon.hide(); 325 } 326 return; 327 } 328 329 if (allowable.contains(str.charAt(0))) { 330 super.insertString(offset, str, a); 331 if (balloon != null && BalloonTipManager.isShowing()) { 332 balloon.hide(); 333 } 334 } else { 335 if (balloon != null && BalloonTipManager.isShowing()) { 336 if (balloonType == ENTRY_BALLOON) { 337 BalloonTipManager.restartTimer(); 338 return; 339 } else { 340 balloon.hide(); 341 } 342 } 343 balloon = BalloonTipManager.getBalloonTip(FilteredTextField.this, 344 entryError, 0, 0, balloonDuration, balloonBorderColor, 345 balloonBackgroundColor, balloonTextColor); 346 balloon.show(); 347 balloonType = ENTRY_BALLOON; 348 } 349 } 350 } 351 352 /* 353 * This listener class is needed to catch character removal events. 354 */ 355 private class FilteredTextFieldEar implements DocumentListener { 356 357 /* 358 * (non-Javadoc) 359 * @see javax.swing.event.DocumentListener#insertUpdate( 360 * javax.swing.event.DocumentEvent) 361 */ insertUpdate(DocumentEvent e)362 public void insertUpdate(DocumentEvent e) {/* N/A */ 363 } 364 365 /* 366 * (non-Javadoc) 367 * @see javax.swing.event.DocumentListener#removeUpdate( 368 * javax.swing.event.DocumentEvent) 369 */ removeUpdate(DocumentEvent e)370 public void removeUpdate(DocumentEvent e) { 371 if (balloon != null && BalloonTipManager.isShowing()) { 372 balloon.hide(); 373 } 374 } 375 376 /* 377 * (non-Javadoc) 378 * @see javax.swing.event.DocumentListener#changedUpdate( 379 * javax.swing.event.DocumentEvent) 380 */ changedUpdate(DocumentEvent e)381 public void changedUpdate(DocumentEvent e) {/* N/A */ 382 } 383 } 384 } 385 386 /* 387 * This listener class is used to determine whether the string is valid based 388 * on a regular expression. The validation is tested when leaving the text 389 * field, and notification is performed when returning to the text field. 390 */ 391 private class ValidationEar extends FocusAdapter { 392 393 /* 394 * (non-Javadoc) 395 * @see java.awt.event.FocusAdapter#focusLost(java.awt.event.FocusEvent) 396 */ focusLost(FocusEvent e)397 public void focusLost(FocusEvent e) { 398 String entered = getText().trim(); 399 if (!entered.matches(validRegex)) { 400 if (balloon != null) { 401 balloon.hide(); 402 } 403 setBorder( 404 BorderFactory.createCompoundBorder(RED_BORDER, defaultBorder)); 405 isValid = false; 406 } else { 407 setBorder(defaultBorder); 408 isValid = true; 409 } 410 } 411 412 /* 413 * (non-Javadoc) 414 * @see java.awt.event.FocusAdapter#focusGained(java.awt.event.FocusEvent) 415 */ focusGained(FocusEvent e)416 public void focusGained(FocusEvent e) { 417 if (!isValid) { 418 balloon = BalloonTipManager.getBalloonTip(FilteredTextField.this, 419 validError, 0, 0, balloonDuration, balloonBorderColor, 420 balloonBackgroundColor, balloonTextColor); 421 balloon.show(); 422 balloonType = VALID_BALLOON; 423 } 424 } 425 } 426 427 /** 428 * A main entry point to test the FilteredTextField. 429 * 430 * @param args application arguments 431 */ main(String[] args)432 public static void main(String[] args) { 433 try { 434 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 435 } catch (Exception e) { 436 e.printStackTrace(); 437 } 438 JFrame jframe = new JFrame("Balloon Tips on FilteredTextField"); 439 jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 440 jframe.setSize(400, 75); 441 jframe.setLocation(400, 400); 442 JPanel jpanel = new JPanel(); 443 jpanel.setLayout(new BorderLayout()); 444 FilteredTextField ftfield = new FilteredTextField(10); 445 ftfield.setCharacters(LOWERCASE_CHARS); 446 ftfield.addCharacter('-'); 447 ftfield.addCharacter('_'); 448 ftfield.addCharacter(' '); 449 ftfield.setMaximumLength(10); 450 ftfield.setEntryError( 451 "Only lower case letters, hyphens, underscores, and spaces allowed."); 452 ftfield.setValidRegex("^a+[a-z-_ ]*"); 453 ftfield.setValidError("The string must begin with the letter 'a'."); 454 jpanel.add(new JLabel("Type some text into either field"), BorderLayout.NORTH); 455 jpanel.add(ftfield, BorderLayout.CENTER); 456 jpanel.add(new FilteredTextField(10), BorderLayout.SOUTH); 457 jframe.getContentPane().add(jpanel); 458 jframe.setVisible(true); 459 } 460 } 461