1 package net.suberic.pooka.gui; 2 import net.suberic.pooka.Pooka; 3 import net.suberic.pooka.AddressBookEntry; 4 import net.suberic.pooka.AddressMatcher; 5 import net.suberic.util.gui.IconManager; 6 import java.awt.event.KeyEvent ; 7 import java.awt.Color ; 8 import java.awt.Font ; 9 import java.util.*; 10 import javax.swing.*; 11 import javax.swing.text.*; 12 import javax.mail.internet.InternetAddress ; 13 14 18 public class AddressEntryTextArea extends net.suberic.util.swing.EntryTextArea implements java.awt.event.FocusListener { 19 static Thread updateThread; 22 23 boolean useCompletion = false; 25 26 static java.util.WeakHashMap areaList = new java.util.WeakHashMap (); 28 29 static int ADDRESS_MATCH = 0; 30 static int VALID = 1; 31 static int INVALID = 2; 32 33 LinkedList addressList = new LinkedList(); 36 37 HashMap addressStatusMap = new HashMap(); 39 40 String lastUpdatedValue = ""; 42 43 44 boolean automaticallyDisplay = false; 46 47 boolean completeNow = false; 49 50 javax.swing.KeyStroke completionKey = javax.swing.KeyStroke.getKeyStroke(net.suberic.pooka.Pooka.getProperty("Pooka.addressComplete", "control D")); 52 53 MessageProxy messageProxy; 55 56 long lastKeyTime = 0; 58 59 long lastMatchedTime; 61 62 int delayInMilliSeconds = 1000; 64 65 Color incompleteColor = Color.red; 68 Color matchedColor = Color.green; 69 Color validColor = Color.blue; 70 71 74 public AddressEntryTextArea(NewMessageUI ui, int rows, int columns) { 75 super(rows, columns); 76 messageProxy = ui.getMessageProxy(); 77 areaList.put(this, null); 78 79 this.addFocusListener(this); 80 81 if (updateThread == null) 82 createUpdateThread(); 83 } 84 85 88 public AddressEntryTextArea(NewMessageUI ui, String text, int rows, int columns) { 89 super(text, rows, columns); 90 messageProxy = ui.getMessageProxy(); 91 areaList.put(this, null); 92 93 useCompletion = Pooka.getProperty("Pooka.useAddressCompletion", "false").equalsIgnoreCase("true"); 94 95 this.addFocusListener(this); 96 } 97 98 102 protected void processComponentKeyEvent(KeyEvent e) { 103 super.processComponentKeyEvent(e); 104 if (useCompletion) { 105 if (e.getID() == KeyEvent.KEY_PRESSED) { 106 int keyCode = e.getKeyCode(); 107 switch(keyCode) { 108 case KeyEvent.VK_TAB: 109 break; 110 case KeyEvent.VK_UP: 111 selectNextEntry(); 112 break; 113 case KeyEvent.VK_DOWN: 114 selectPreviousEntry(); 115 break; 116 case KeyEvent.VK_LEFT: 117 break; 119 case KeyEvent.VK_RIGHT: 120 break; 122 default: 123 lastKeyTime = System.currentTimeMillis(); 126 if (updateThread != null) 127 updateThread.interrupt(); 128 else 129 createUpdateThread(); 130 131 if (keyCode == completionKey.getKeyCode() && e.getModifiers() == completionKey.getModifiers()) { 132 completeNow = true; 133 } 134 } 135 } 136 } 137 } 138 139 143 protected void updateTextValue() { 144 final long lastModifiedTime = lastKeyTime; 145 net.suberic.pooka.AddressMatcher matcher = getNewMessageUI().getSelectedProfile().getAddressMatcher(); 146 147 if (matcher != null) { 148 final String entryString = getAddressText(); 149 150 if (needToMatch(entryString)) { 151 final net.suberic.pooka.AddressBookEntry[] matchedEntries = matcher.match(entryString); 152 153 try { 154 SwingUtilities.invokeAndWait(new Runnable () { 155 public void run() { 156 if (lastModifiedTime == lastKeyTime) { 158 if (matchedEntries.length > 0) { 159 String newAddress = matchedEntries[0].getID(); 160 if (!newAddress.equalsIgnoreCase(entryString)) 161 updateAddressText(newAddress); 162 } else { 163 updateAddressText(entryString + Pooka.getProperty("error.noMatchingAddresses", "<no matching addresses>")); 164 } 165 166 lastMatchedTime = System.currentTimeMillis(); 167 } 168 } 169 }); 170 } catch (Exception e) { 171 } 172 } 173 } 174 } 175 176 179 protected synchronized void updateAddressList() { 180 updateAddressList(false); 181 } 182 183 186 protected synchronized void updateAddressList(boolean inThread) { 187 final String currentText = getText(); 188 if (!currentText.equals(lastUpdatedValue)) { 189 LinkedList newAddressList = new LinkedList(); 190 int beginOffset = 0; 191 boolean done = false; 192 while (! done) { 193 int endOffset = currentText.indexOf(',', beginOffset); 194 if (endOffset == -1) { 195 endOffset = currentText.length(); 196 done = true; 197 } 198 Selection currentSelection = new Selection(beginOffset, endOffset, currentText.substring(beginOffset, endOffset)); 199 currentSelection.status = parseStatus(currentSelection); 200 newAddressList.add(currentSelection); 201 beginOffset = endOffset + 1; 202 if (beginOffset >= currentText.length()) 203 done = true; 204 } 205 206 final LinkedList toUpdateList = newAddressList; 207 208 if (! inThread) { 209 SwingUtilities.invokeLater(new Runnable () { 210 public void run() { 211 updateParsedSelections(toUpdateList, currentText); 212 } 213 }); 214 } else { 215 if (SwingUtilities.isEventDispatchThread()) 216 updateParsedSelections(toUpdateList, currentText); 217 else 218 try { 219 SwingUtilities.invokeAndWait(new Runnable () { 220 public void run() { 221 updateParsedSelections(toUpdateList, currentText); 222 } 223 }); 224 } catch (Exception e) { 225 226 } 227 } 228 } 229 } 230 231 238 public SelectionStatus parseStatus(Selection current) { 239 String addressText = current.text.trim(); 240 Object value = addressStatusMap.get(addressText); 241 if (value != null) { 242 return (SelectionStatus) value; 243 } else { 244 SelectionStatus status = null; 245 246 net.suberic.pooka.AddressMatcher matcher = getNewMessageUI().getSelectedProfile().getAddressMatcher(); 248 if (matcher != null) { 249 AddressBookEntry[] matchedEntries = matcher.matchExactly(addressText); 250 if (matchedEntries != null && matchedEntries.length > 0) { 251 status = new SelectionStatus(matchedEntries[0].getAddressString(), ADDRESS_MATCH); 252 } 253 } 254 255 if (status == null) { 256 try { 258 InternetAddress newAddress = new InternetAddress (addressText); 259 status = new SelectionStatus(addressText, VALID); 260 } catch (javax.mail.internet.AddressException ae) { 261 status = new SelectionStatus(addressText, INVALID); 262 } 263 } 264 addressStatusMap.put(addressText, status); 265 return status; 266 } 267 } 268 269 280 public void updateParsedSelections(LinkedList newAddressList, String parsedText) { 281 if (parsedText.equals(getText())) { 282 Iterator iter = newAddressList.iterator(); 283 while (iter.hasNext()) { 284 Selection current = (Selection) iter.next(); 285 changeSelectionFont(current.beginOffset, current.endOffset, current.status.status); 286 } 287 288 lastUpdatedValue = parsedText; 289 addressList = newAddressList; 290 } 291 } 292 293 297 public boolean validateAddressList() { 298 String currentText = getText(); 299 int caretPos = getCaretPosition(); 300 java.util.Iterator iter = addressList.iterator(); 301 boolean matches = true; 302 303 while (matches && iter.hasNext()) { 304 Selection current = (Selection) iter.next(); 305 if (! currentText.substring(current.beginOffset, current.endOffset).equals(current.text)) { 306 matches = false; 307 } 308 } 309 310 return matches; 311 } 312 313 318 void changeSelectionFont(int beginOffset, int endOffset, int status) { 319 334 } 335 336 339 public boolean needToMatch(String entry) { 340 if (entry.length() == 0) 341 return false; 342 else 343 return true; 344 } 345 346 349 public String getAddressText() { 350 Selection currentSelection = getCurrentSelection(); 351 return currentSelection.text; 352 } 353 354 357 public String getParsedAddresses() { 358 updateAddressList(true); 359 StringBuffer returnBuffer = new StringBuffer (); 360 361 Iterator iter = addressList.iterator(); 362 while (iter.hasNext()) { 363 Selection current = (Selection) iter.next(); 364 returnBuffer.append(current.status.addressText); 365 if (iter.hasNext()) 366 returnBuffer.append(", "); 367 } 368 369 return returnBuffer.toString(); 370 } 371 372 375 Selection getCurrentSelection() { 376 int caretPosition = getCaretPosition(); 377 378 String currentText = getText(); 379 380 int beginOffset = 0; 383 if (caretPosition > 0) 384 beginOffset = currentText.lastIndexOf(',', caretPosition - 1) +1; 385 else 386 beginOffset = 0; 387 int endOffset = currentText.indexOf(',', caretPosition); 388 if (beginOffset < 0) 389 beginOffset = 0; 390 if (endOffset < 0) 391 endOffset = currentText.length(); 392 393 while(beginOffset < endOffset && Character.isWhitespace(currentText.charAt(beginOffset))) 395 beginOffset++; 396 397 return new Selection(beginOffset, endOffset, currentText.substring(beginOffset, endOffset)); 398 } 399 400 403 public void updateAddressText(String newAddress) { 404 Selection current = getCurrentSelection(); 405 int length = current.text.length(); 406 this.insert(newAddress.substring(length), current.beginOffset + length); 408 409 417 418 this.setSelectionStart(current.beginOffset + length); 419 this.setSelectionEnd(current.beginOffset + newAddress.length()); 420 } 421 422 425 public void replaceAddressText(Selection current, String newAddress) { 426 int length = current.text.length(); 427 try { 430 getDocument().remove(current.beginOffset, current.endOffset - current.beginOffset); 431 getDocument().insertString(current.beginOffset, newAddress, null); 432 } catch (BadLocationException ble) { 433 ble.printStackTrace(); 434 } 435 this.setSelectionStart(current.beginOffset); 436 this.setSelectionEnd(current.beginOffset + newAddress.length()); 437 } 438 439 442 public void selectNextEntry() { 443 Selection currentSelection = getCurrentSelection(); 444 net.suberic.pooka.AddressMatcher matcher = getNewMessageUI().getSelectedProfile().getAddressMatcher(); 445 AddressBookEntry newValue = matcher.getNextMatch(currentSelection.text); 446 if (newValue != null) { 447 replaceAddressText(currentSelection, newValue.getID()); 448 } 449 450 } 451 452 455 public void selectPreviousEntry() { 456 Selection currentSelection = getCurrentSelection(); 457 net.suberic.pooka.AddressMatcher matcher = getNewMessageUI().getSelectedProfile().getAddressMatcher(); 458 if (matcher != null) { 459 AddressBookEntry newValue = matcher.getPreviousMatch(currentSelection.text); 460 if (newValue != null) { 461 replaceAddressText(currentSelection, newValue.getID()); 462 } 463 } 464 } 465 466 469 public void addAddresses(AddressBookEntry[] newEntries) { 470 if (newEntries == null || newEntries.length < 1) 472 return; 473 474 String currentValue = getText(); 476 boolean addComma = false; 477 boolean found = false; 478 for (int i = currentValue.length() - 1; !found && i >=0; i--) { 479 char currentChar = currentValue.charAt(i); 480 if (! Character.isWhitespace(currentChar)) { 481 found = true; 482 if (currentChar != ',') 483 addComma = true; 484 } 485 } 486 487 StringBuffer newValue = new StringBuffer (currentValue); 488 if (addComma) 489 newValue.append(", "); 490 491 for (int i = 0; i < newEntries.length; i++) { 492 newValue.append(newEntries[i].getAddressString()); 493 if (i < newEntries.length -1) 494 newValue.append(", "); 495 } 496 497 setText(newValue.toString()); 498 } 499 500 static java.util.HashMap buttonImageMap = new java.util.HashMap (); 501 502 505 public JButton createAddressButton(int width, int height) { 506 java.awt.Dimension key = new java.awt.Dimension (width, height); 507 java.awt.Image defaultImage = (java.awt.Image ) buttonImageMap.get(key); 508 if (defaultImage == null) { 509 ImageIcon addressIcon = Pooka.getUIFactory().getIconManager().getIcon(Pooka.getProperty("AddressBook.button", "Book")); 510 511 if (addressIcon != null) { 512 java.awt.Image addressImage = addressIcon.getImage(); 513 defaultImage = addressImage.getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH); 514 addressIcon.setImage(defaultImage); 515 buttonImageMap.put(key, defaultImage); 516 } 517 } 518 519 JButton returnValue = null; 520 if (defaultImage != null) { 521 ImageIcon addressIcon = new ImageIcon(defaultImage); 522 returnValue = new JButton(addressIcon); 523 } else { 524 returnValue=new JButton(); 525 } 526 returnValue.setFocusable(false); 527 returnValue.addActionListener(new AbstractAction() { 528 public void actionPerformed(java.awt.event.ActionEvent e) { 529 getNewMessageUI().showAddressWindow(AddressEntryTextArea.this); 530 } 531 }); 532 533 returnValue.setToolTipText(Pooka.getProperty("AddressBookEditor.buttonText", "Open Address Book")); 534 returnValue.setMargin(new java.awt.Insets (1,1,1,1)); 535 returnValue.setSize(width, height); 536 return returnValue; 537 } 538 539 542 public NewMessageUI getNewMessageUI() { 543 return (NewMessageUI) messageProxy.getMessageUI(); 544 } 545 546 private class Selection { 547 int beginOffset; 548 int endOffset; 549 String text; 550 SelectionStatus status = null; 551 552 Selection(int newBegin, int newEnd, String newText) { 553 beginOffset = newBegin; 554 endOffset = newEnd; 555 text = newText; 556 } 557 } 558 559 private class SelectionStatus { 560 String addressText = null; 561 int status; 562 563 SelectionStatus(String newAddressText, int newStatus) { 564 addressText = newAddressText; 565 status = newStatus; 566 } 567 } 568 572 public void focusGained(java.awt.event.FocusEvent e) { 573 574 } 575 576 579 public void focusLost(java.awt.event.FocusEvent e) { 580 lastMatchedTime = System.currentTimeMillis(); 581 } 582 583 585 static synchronized void createUpdateThread() { 586 if (updateThread == null) { 587 updateThread = new Thread (new Updater(), "AddressEntryTextArea - Update Thread"); 588 updateThread.start(); 589 } 590 } 591 592 static class Updater implements Runnable { 593 594 long sleepTime = 60000; 595 596 Updater() { 597 } 598 599 public void run() { 600 sleepTime = 0; 601 java.util.Set entrySet = areaList.entrySet(); 602 while(! entrySet.isEmpty()) { 603 sleepTime = 60000; 604 java.util.Iterator entryIter = entrySet.iterator(); 605 while (entryIter.hasNext()) { 606 long currentTime = System.currentTimeMillis(); 607 AddressEntryTextArea area = (AddressEntryTextArea) ((java.util.Map.Entry)entryIter.next()).getKey(); 608 if (area.lastKeyTime > area.lastMatchedTime) { 609 if (area.lastKeyTime + area.delayInMilliSeconds < currentTime) { 610 if (area.completeNow || area.automaticallyDisplay) { 611 area.completeNow = false; 612 area.updateTextValue(); 613 } 614 615 area.updateAddressList(); 616 617 } else { 618 sleepTime = Math.min(sleepTime, (area.delayInMilliSeconds + area.lastKeyTime) - currentTime); 619 } 620 } 621 } 622 623 try { 624 Thread.currentThread().sleep(sleepTime); 625 } catch (InterruptedException e) { 626 } 627 } 628 } 629 } 630 631 } 632 | Popular Tags |