KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > suberic > pooka > gui > AddressEntryTextArea


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 JavaDoc;
7 import java.awt.Color JavaDoc;
8 import java.awt.Font JavaDoc;
9 import java.util.*;
10 import javax.swing.*;
11 import javax.swing.text.*;
12 import javax.mail.internet.InternetAddress JavaDoc;
13
14 /**
15  *<p> This is a JTextArea which uses an AddressMatcher to fill in completed
16  * addresses. It also will store
17  */

18 public class AddressEntryTextArea extends net.suberic.util.swing.EntryTextArea implements java.awt.event.FocusListener JavaDoc {
19   //---------- static variables -------------//
20
// the update thread for all AddressEntryTextAreas
21
static Thread JavaDoc updateThread;
22
23   // whether or not we're using keyboard completion.
24
boolean useCompletion = false;
25
26   // the list of all AddressEntryTextAreas
27
static java.util.WeakHashMap JavaDoc areaList = new java.util.WeakHashMap JavaDoc();
28
29   static int ADDRESS_MATCH = 0;
30   static int VALID = 1;
31   static int INVALID = 2;
32
33   //---------- instance variables -----------//
34
// the list of Addresses
35
LinkedList addressList = new LinkedList();
36
37   // a map of looked-up values and their associated statuses
38
HashMap addressStatusMap = new HashMap();
39
40   // the last text value updated.
41
String JavaDoc lastUpdatedValue = "";
42
43
44   // if we're doing this by delay or by keystroke
45
boolean automaticallyDisplay = false;
46
47   // flag for if we've pressed the complete key.
48
boolean completeNow = false;
49
50   // if by keystroke, the key that is used to request address completion
51
javax.swing.KeyStroke JavaDoc completionKey = javax.swing.KeyStroke.getKeyStroke(net.suberic.pooka.Pooka.getProperty("Pooka.addressComplete", "control D"));
52
53   // the underlying MessageProxy
54
MessageProxy messageProxy;
55
56   // the last time this field got a key hit
57
long lastKeyTime = 0;
58
59   // the last time this field was updated
60
long lastMatchedTime;
61
62   // the delay in milliseconds between the last key hit and the next update.
63
int delayInMilliSeconds = 1000;
64
65   // if we're updating the display if we match, or if an address is
66
// incomplete, then these are the colors that we'll use to notify.
67
Color JavaDoc incompleteColor = Color.red;
68   Color JavaDoc matchedColor = Color.green;
69   Color JavaDoc validColor = Color.blue;
70   
71   /**
72    * Creates a new AddressEntryTextArea using the given NewMessageUI.
73    */

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   /**
86    * Creates a new AddressEntryTextArea using the given NewMessageUI.
87    */

88   public AddressEntryTextArea(NewMessageUI ui, String JavaDoc 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   /**
99    * Makes it so that we listen for key events. On a key event, we update
100    * the last time a key was pressed.
101    */

102   protected void processComponentKeyEvent(KeyEvent JavaDoc 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       // ignore
118
break;
119     case KeyEvent.VK_RIGHT:
120       // ignore
121
break;
122     default:
123       // we're just going to have to look at updating the text area
124
// all the freakin' time. :)
125
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   /**
140    * After a sufficient amount of time has passed, updates the entry area
141    * with a found value. Called by the updateThread.
142    */

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 JavaDoc 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 JavaDoc() {
155           public void run() {
156         // make sure no keys have been pressed since we did the match.
157
if (lastModifiedTime == lastKeyTime) {
158           if (matchedEntries.length > 0) {
159             String JavaDoc 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 JavaDoc e) {
171     }
172       }
173     }
174   }
175
176   /**
177    * This updates the list of matched addresses in this entry field.
178    */

179   protected synchronized void updateAddressList() {
180     updateAddressList(false);
181   }
182
183   /**
184    * This updates the list of matched addresses in this entry field.
185    */

186   protected synchronized void updateAddressList(boolean inThread) {
187     final String JavaDoc 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 JavaDoc() {
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 JavaDoc() {
220         public void run() {
221           updateParsedSelections(toUpdateList, currentText);
222         }
223           });
224       } catch (Exception JavaDoc e) {
225         
226       }
227       }
228     }
229   }
230
231   /**
232    * <p>Checks a selection to see if it's a correctly parsed address, an
233    * address book entry, or neither.</p>
234    *
235    * <p>This method also updates the addressStatusMap with any parsed
236    * values.</p>
237    */

238   public SelectionStatus parseStatus(Selection current) {
239     String JavaDoc addressText = current.text.trim();
240     Object JavaDoc value = addressStatusMap.get(addressText);
241     if (value != null) {
242       return (SelectionStatus) value;
243     } else {
244       SelectionStatus status = null;
245
246       // first see if we're an address book entry.
247
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     // check to see if it's a valid address
257
try {
258       InternetAddress JavaDoc newAddress = new InternetAddress JavaDoc(addressText);
259       status = new SelectionStatus(addressText, VALID);
260     } catch (javax.mail.internet.AddressException JavaDoc ae) {
261       status = new SelectionStatus(addressText, INVALID);
262     }
263       }
264       addressStatusMap.put(addressText, status);
265       return status;
266     }
267   }
268
269   /**
270    * <p>This redraws the text with the fonts and colors to represent the
271    * separate fields' statuses.</p>
272    *
273    * <p>The method will first check to make sure that the text hasn't
274    * changed since the last update. If it hasn't, then it will update the
275    * text, and then write in the new addressList and lastUpdatedValue
276    * fields.</p>
277    *
278    * <p>This method should only be called from the SwingEvent thread.</p>
279    */

280   public void updateParsedSelections(LinkedList newAddressList, String JavaDoc 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   /**
294    * This validates that the current addressList matches the actual
295    * text in the field.
296    */

297   public boolean validateAddressList() {
298     String JavaDoc currentText = getText();
299     int caretPos = getCaretPosition();
300     java.util.Iterator JavaDoc 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   /**
314    * <p>Changes the text in the area between the <code>beginOffset<code> and
315    * <code>endOffset</code>
316    * to the font appropriate for status <code>status</code>.
317    */

318   void changeSelectionFont(int beginOffset, int endOffset, int status) {
319     // implement me later, when you can get wrapping to work.
320
/*
321     javax.swing.text.StyledDocument doc = (javax.swing.text.StyledDocument)getDocument();
322     StyledEditorKit kit = (StyledEditorKit) getEditorKit();
323     MutableAttributeSet attr = kit.getInputAttributes();
324     SimpleAttributeSet sas = new SimpleAttributeSet();
325     if (status == ADDRESS_MATCH) {
326       StyleConstants.setForeground(sas, matchedColor);
327     } else if (status == VALID) {
328       StyleConstants.setForeground(sas, validColor);
329     } else {
330       StyleConstants.setForeground(sas, incompleteColor);
331     }
332     doc.setCharacterAttributes(beginOffset, (endOffset - beginOffset), sas, false);
333     */

334   }
335
336   /**
337    * This tests to see if the given string needs to be matched or not.
338    */

339   public boolean needToMatch(String JavaDoc entry) {
340     if (entry.length() == 0)
341       return false;
342     else
343       return true;
344   }
345
346   /**
347    * This gets the currently selected address field.
348    */

349   public String JavaDoc getAddressText() {
350     Selection currentSelection = getCurrentSelection();
351     return currentSelection.text;
352   }
353
354   /**
355    * This gets the parsed address text for this feel.
356    */

357   public String JavaDoc getParsedAddresses() {
358     updateAddressList(true);
359     StringBuffer JavaDoc returnBuffer = new StringBuffer JavaDoc();
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   /**
373    * Gets the current Selection.
374    */

375   Selection getCurrentSelection() {
376     int caretPosition = getCaretPosition();
377
378     String JavaDoc currentText = getText();
379
380     // get the area bounded by commas, or by the beginning and end of
381
// the text.
382
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     // strip whitespace
394
while(beginOffset < endOffset && Character.isWhitespace(currentText.charAt(beginOffset)))
395       beginOffset++;
396
397     return new Selection(beginOffset, endOffset, currentText.substring(beginOffset, endOffset));
398   }
399   
400   /**
401    * This updates the currently selected address field with the new value.
402    */

403   public void updateAddressText(String JavaDoc newAddress) {
404     Selection current = getCurrentSelection();
405     int length = current.text.length();
406     // the text should always match the newAddress. really. :)
407
this.insert(newAddress.substring(length), current.beginOffset + length);
408
409     // for use when we get the JTextPane to wrap.
410
/*
411     try {
412       getDocument().insertString(current.beginOffset + length, newAddress.substring(length), null);
413     } catch (BadLocationException ble) {
414       ble.printStackTrace();
415     }
416     */

417
418     this.setSelectionStart(current.beginOffset + length);
419     this.setSelectionEnd(current.beginOffset + newAddress.length());
420   }
421
422   /**
423    * This updates the currently selected address field with the new value.
424    */

425   public void replaceAddressText(Selection current, String JavaDoc newAddress) {
426     int length = current.text.length();
427     // the text should always match the newAddress. really. :)
428
//this.replaceRange(newAddress, current.beginOffset, current.endOffset);
429
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   /**
440    * Selects the next available address entry.
441    */

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   /**
453    * Selects the previous available address entry.
454    */

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   /**
467    * Adds the given AddressBookEntries to the address field.
468    */

469   public void addAddresses(AddressBookEntry[] newEntries) {
470     // first see if we're actually adding anything
471
if (newEntries == null || newEntries.length < 1)
472       return;
473
474     // next see if we need to add a comma.
475
String JavaDoc 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 JavaDoc newValue = new StringBuffer JavaDoc(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 JavaDoc buttonImageMap = new java.util.HashMap JavaDoc();
501
502   /**
503    * Creates a button that pulls up an editor dialog for addresses.
504    */

505   public JButton createAddressButton(int width, int height) {
506     java.awt.Dimension JavaDoc key = new java.awt.Dimension JavaDoc(width, height);
507     java.awt.Image JavaDoc defaultImage = (java.awt.Image JavaDoc) 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 JavaDoc 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 JavaDoc 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 JavaDoc(1,1,1,1));
535     returnValue.setSize(width, height);
536     return returnValue;
537   }
538
539   /**
540    * Returns the parent MessageUI.
541    */

542   public NewMessageUI getNewMessageUI() {
543     return (NewMessageUI) messageProxy.getMessageUI();
544   }
545
546   private class Selection {
547     int beginOffset;
548     int endOffset;
549     String JavaDoc text;
550     SelectionStatus status = null;
551
552     Selection(int newBegin, int newEnd, String JavaDoc newText) {
553       beginOffset = newBegin;
554       endOffset = newEnd;
555       text = newText;
556     }
557   }
558
559   private class SelectionStatus {
560     String JavaDoc addressText = null;
561     int status;
562     
563     SelectionStatus(String JavaDoc newAddressText, int newStatus) {
564       addressText = newAddressText;
565       status = newStatus;
566     }
567   }
568   //----------- focus listener ----------------
569
/**
570    * a no-op -- don't do anything on focusGained.
571    */

572   public void focusGained(java.awt.event.FocusEvent JavaDoc e) {
573     
574   }
575
576   /**
577    *
578    */

579   public void focusLost(java.awt.event.FocusEvent JavaDoc e) {
580     lastMatchedTime = System.currentTimeMillis();
581   }
582
583   //----------- updater thread ----------------
584

585   static synchronized void createUpdateThread() {
586     if (updateThread == null) {
587       updateThread = new Thread JavaDoc(new Updater(), "AddressEntryTextArea - Update Thread");
588       updateThread.start();
589     }
590   }
591
592   static class Updater implements Runnable JavaDoc {
593
594     long sleepTime = 60000;
595
596     Updater() {
597     }
598
599     public void run() {
600       sleepTime = 0;
601       java.util.Set JavaDoc entrySet = areaList.entrySet();
602       while(! entrySet.isEmpty()) {
603     sleepTime = 60000;
604     java.util.Iterator JavaDoc 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 JavaDoc e) {
626     }
627       }
628     }
629   }
630
631 }
632
Popular Tags