Package net.suberic.pooka.gui

Source Code of net.suberic.pooka.gui.AddressEntryTextArea

package net.suberic.pooka.gui;
import net.suberic.pooka.Pooka;
import net.suberic.pooka.AddressBookEntry;
import net.suberic.pooka.AddressMatcher;
import net.suberic.util.gui.IconManager;
import java.awt.event.KeyEvent;
import java.awt.Color;
import java.awt.Font;
import java.util.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.mail.internet.InternetAddress;

/**
*<p> This is a JTextArea which uses an AddressMatcher to fill in completed
* addresses.  It also will store
*/
public class AddressEntryTextArea extends net.suberic.util.swing.EntryTextArea implements java.awt.event.FocusListener {
  //---------- static variables -------------//
  // the update thread for all AddressEntryTextAreas
  static Thread updateThread;

  // whether or not we're using keyboard completion.
  boolean useCompletion = false;

  // the list of all AddressEntryTextAreas
  static java.util.WeakHashMap areaList = new java.util.WeakHashMap();

  static int ADDRESS_MATCH = 0;
  static int VALID = 1;
  static int INVALID = 2;

  //---------- instance variables -----------//
  // the list of Addresses
  LinkedList addressList = new LinkedList();

  // a map of looked-up values and their associated statuses
  HashMap addressStatusMap = new HashMap();

  // the last text value updated.
  String lastUpdatedValue = "";


  // if we're doing this by delay or by keystroke
  boolean automaticallyDisplay = false;

  // flag for if we've pressed the complete key.
  boolean completeNow = false;

  // if by keystroke, the key that is used to request address completion
  javax.swing.KeyStroke completionKey = javax.swing.KeyStroke.getKeyStroke(net.suberic.pooka.Pooka.getProperty("Pooka.addressComplete", "control D"));

  // the underlying MessageProxy
  MessageProxy messageProxy;

  // the last time this field got a key hit
  long lastKeyTime = 0;

  // the last time this field was updated
  long lastMatchedTime;

  // the delay in milliseconds between the last key hit and the next update.
  int delayInMilliSeconds = 1000;

  // if we're updating the display if we match, or if an address is
  // incomplete, then these are the colors that we'll use to notify.
  Color incompleteColor = Color.red;
  Color matchedColor = Color.green;
  Color validColor = Color.blue;
 
  /**
   * Creates a new AddressEntryTextArea using the given NewMessageUI.
   */
  public AddressEntryTextArea(NewMessageUI ui, int rows, int columns) {
    super(rows, columns);
    messageProxy = ui.getMessageProxy();
    areaList.put(this, null);

    this.addFocusListener(this);
   
    if (updateThread == null)
      createUpdateThread();
  }

  /**
   * Creates a new AddressEntryTextArea using the given NewMessageUI.
   */
  public AddressEntryTextArea(NewMessageUI ui, String text, int rows, int columns) {
    super(text, rows, columns);
    messageProxy = ui.getMessageProxy();
    areaList.put(this, null);

    useCompletion = Pooka.getProperty("Pooka.useAddressCompletion", "false").equalsIgnoreCase("true");

    this.addFocusListener(this);
  }

  /**
   * Makes it so that we listen for key events.  On a key event, we update
   * the last time a key was pressed.
   */
  protected void processComponentKeyEvent(KeyEvent e) {
    super.processComponentKeyEvent(e);
    if (useCompletion) {
      if (e.getID() == KeyEvent.KEY_PRESSED) {
  int keyCode = e.getKeyCode();
  switch(keyCode) {
  case KeyEvent.VK_TAB:
    break;
  case KeyEvent.VK_UP:
    selectNextEntry();
    break;
  case KeyEvent.VK_DOWN:
    selectPreviousEntry();
    break;
  case KeyEvent.VK_LEFT:
    // ignore
    break;
  case KeyEvent.VK_RIGHT:
    // ignore
    break;
  default:
    // we're just going to have to look at updating the text area
    // all the freakin' time.  :)
    lastKeyTime = System.currentTimeMillis();
    if (updateThread != null)
      updateThread.interrupt();
    else
      createUpdateThread();
   
    if (keyCode == completionKey.getKeyCode() && e.getModifiers() == completionKey.getModifiers()) {
      completeNow = true;
    }
  }
      }
    }
  }

  /**
   * After a sufficient amount of time has passed, updates the entry area
   * with a found value.  Called by the updateThread.
   */
  protected void updateTextValue() {
    final long lastModifiedTime = lastKeyTime;
    net.suberic.pooka.AddressMatcher matcher = getNewMessageUI().getSelectedProfile().getAddressMatcher();

    if (matcher != null) {
      final String entryString = getAddressText();
     
      if (needToMatch(entryString)) {
  final net.suberic.pooka.AddressBookEntry[] matchedEntries = matcher.match(entryString);
 
  try {
    SwingUtilities.invokeAndWait(new Runnable() {
        public void run() {
    // make sure no keys have been pressed since we did the match.
    if (lastModifiedTime == lastKeyTime) {
      if (matchedEntries.length > 0) {
        String newAddress = matchedEntries[0].getID();
        if (!newAddress.equalsIgnoreCase(entryString))
          updateAddressText(newAddress);
      } else {
        updateAddressText(entryString + Pooka.getProperty("error.noMatchingAddresses", "<no matching addresses>"));
      }
     
      lastMatchedTime = System.currentTimeMillis();
    }
        }
      });
  } catch (Exception e) {
  }
      }
    }
  }

  /**
   * This updates the list of matched addresses in this entry field.
   */
  protected synchronized void updateAddressList() {
    updateAddressList(false);
  }

  /**
   * This updates the list of matched addresses in this entry field.
   */
  protected synchronized void updateAddressList(boolean inThread) {
    final String currentText = getText();
    if (!currentText.equals(lastUpdatedValue)) {
      LinkedList newAddressList = new LinkedList();
      int beginOffset = 0;
      boolean done = false;
      while (! done) {
  int endOffset = currentText.indexOf(',', beginOffset);
  if (endOffset == -1) {
    endOffset = currentText.length();
    done = true;
  }
  Selection currentSelection = new Selection(beginOffset, endOffset, currentText.substring(beginOffset, endOffset));
  currentSelection.status = parseStatus(currentSelection);
  newAddressList.add(currentSelection);
  beginOffset = endOffset + 1;
  if (beginOffset >= currentText.length())
    done = true;
      }

      final LinkedList toUpdateList = newAddressList;

      if (! inThread) {
  SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        updateParsedSelections(toUpdateList, currentText);
      }
    });
      } else {
  if (SwingUtilities.isEventDispatchThread())
    updateParsedSelections(toUpdateList, currentText);
  else
    try {
      SwingUtilities.invokeAndWait(new Runnable() {
    public void run() {
      updateParsedSelections(toUpdateList, currentText);
    }
        });
    } catch (Exception e) {
     
    }
      }
    }
  }

  /**
   * <p>Checks a selection to see if it's a correctly parsed address, an
   * address book entry, or neither.</p>
   *
   * <p>This method also updates the addressStatusMap with any parsed
   * values.</p>
   */
  public SelectionStatus parseStatus(Selection current) {
    String addressText = current.text.trim();
    Object value = addressStatusMap.get(addressText);
    if (value != null) {
      return (SelectionStatus) value;
    } else {
      SelectionStatus status = null;

      // first see if we're an address book entry.
      net.suberic.pooka.AddressMatcher matcher = getNewMessageUI().getSelectedProfile().getAddressMatcher();
      if (matcher != null) {
  AddressBookEntry[] matchedEntries = matcher.matchExactly(addressText);
  if (matchedEntries != null && matchedEntries.length > 0) {
    status = new SelectionStatus(matchedEntries[0].getAddressString(), ADDRESS_MATCH);
  }
      }

      if (status == null) {
  // check to see if it's a valid address
  try {
    InternetAddress newAddress = new InternetAddress(addressText);
    status = new SelectionStatus(addressText, VALID);
  } catch (javax.mail.internet.AddressException ae) {
    status = new SelectionStatus(addressText, INVALID);
  }
      }
      addressStatusMap.put(addressText, status);
      return status;
    }
  }

  /**
   * <p>This redraws the text with the fonts and colors to represent the
   * separate fields' statuses.</p>
   *
   * <p>The method will first check to make sure that the text hasn't
   * changed since the last update.  If it hasn't, then it will update the
   * text, and then write in the new addressList and lastUpdatedValue
   * fields.</p>
   *
   * <p>This method should only be called from the SwingEvent thread.</p>
   */
  public void updateParsedSelections(LinkedList newAddressList, String parsedText) {
    if (parsedText.equals(getText())) {
      Iterator iter = newAddressList.iterator();
      while (iter.hasNext()) {
  Selection current = (Selection) iter.next();
  changeSelectionFont(current.beginOffset, current.endOffset, current.status.status);
      }

      lastUpdatedValue = parsedText;
      addressList = newAddressList;
    }
  }

  /**
   * This validates that the current addressList matches the actual
   * text in the field.
   */
  public boolean validateAddressList() {
    String currentText = getText();
    int caretPos = getCaretPosition();
    java.util.Iterator iter = addressList.iterator();
    boolean matches = true;
   
    while (matches && iter.hasNext()) {
      Selection current = (Selection) iter.next();
      if (! currentText.substring(current.beginOffset, current.endOffset).equals(current.text)) {
  matches = false;
      }
    }
   
    return matches;
  }

  /**
   * <p>Changes the text in the area between the <code>beginOffset<code> and
   * <code>endOffset</code>
   * to the font appropriate for status <code>status</code>.
   */
  void changeSelectionFont(int beginOffset, int endOffset, int status) {
    // implement me later, when you can get wrapping to work.
    /*
    javax.swing.text.StyledDocument doc = (javax.swing.text.StyledDocument)getDocument();
    StyledEditorKit kit = (StyledEditorKit) getEditorKit();
    MutableAttributeSet attr = kit.getInputAttributes();
    SimpleAttributeSet sas = new SimpleAttributeSet();
    if (status == ADDRESS_MATCH) {
      StyleConstants.setForeground(sas, matchedColor);
    } else if (status == VALID) {
      StyleConstants.setForeground(sas, validColor);
    } else {
      StyleConstants.setForeground(sas, incompleteColor);
    }
    doc.setCharacterAttributes(beginOffset, (endOffset - beginOffset), sas, false);
    */
  }

  /**
   * This tests to see if the given string needs to be matched or not.
   */
  public boolean needToMatch(String entry) {
    if (entry.length() == 0)
      return false;
    else
      return true;
  }

  /**
   * This gets the currently selected address field.
   */
  public String getAddressText() {
    Selection currentSelection = getCurrentSelection();
    return currentSelection.text;
  }

  /**
   * This gets the parsed address text for this feel.
   */
  public String getParsedAddresses() {
    updateAddressList(true);
    StringBuffer returnBuffer = new StringBuffer();

    Iterator iter = addressList.iterator();
    while (iter.hasNext()) {
      Selection current = (Selection) iter.next();
      returnBuffer.append(current.status.addressText);
      if (iter.hasNext())
  returnBuffer.append(", ");
    }
   
    return returnBuffer.toString();
  }
 
  /**
   * Gets the current Selection.
   */
  Selection getCurrentSelection() {
    int caretPosition = getCaretPosition();

    String currentText = getText();

    // get the area bounded by commas, or by the beginning and end of
    // the text.
    int beginOffset = 0;
    if (caretPosition > 0)
      beginOffset = currentText.lastIndexOf(',', caretPosition - 1) +1;
    else
      beginOffset = 0;
    int endOffset = currentText.indexOf(',', caretPosition);
    if (beginOffset < 0)
      beginOffset = 0;
    if (endOffset < 0)
      endOffset = currentText.length();
   
    // strip whitespace
    while(beginOffset < endOffset && Character.isWhitespace(currentText.charAt(beginOffset)))
      beginOffset++;

    return new Selection(beginOffset, endOffset, currentText.substring(beginOffset, endOffset));
  }
 
  /**
   * This updates the currently selected address field with the new value.
   */
  public void updateAddressText(String newAddress) {
    Selection current = getCurrentSelection();
    int length = current.text.length();
    // the text should always match the newAddress.  really.  :)
    this.insert(newAddress.substring(length), current.beginOffset + length);

    // for use when we get the JTextPane to wrap.
    /*
    try {
      getDocument().insertString(current.beginOffset + length, newAddress.substring(length), null);
    } catch (BadLocationException ble) {
      ble.printStackTrace();
    }
    */

    this.setSelectionStart(current.beginOffset + length);
    this.setSelectionEnd(current.beginOffset + newAddress.length());
  }

  /**
   * This updates the currently selected address field with the new value.
   */
  public void replaceAddressText(Selection current, String newAddress) {
    int length = current.text.length();
    // the text should always match the newAddress.  really.  :)
    //this.replaceRange(newAddress, current.beginOffset, current.endOffset);
    try {
      getDocument().remove(current.beginOffset, current.endOffset - current.beginOffset);
      getDocument().insertString(current.beginOffset, newAddress, null);
    } catch (BadLocationException ble) {
      ble.printStackTrace();
    }
    this.setSelectionStart(current.beginOffset);
    this.setSelectionEnd(current.beginOffset + newAddress.length());
  }

  /**
   * Selects the next available address entry.
   */
  public void selectNextEntry() {
    Selection currentSelection = getCurrentSelection();
    net.suberic.pooka.AddressMatcher matcher = getNewMessageUI().getSelectedProfile().getAddressMatcher();
    AddressBookEntry newValue = matcher.getNextMatch(currentSelection.text);
    if (newValue != null) {
      replaceAddressText(currentSelection, newValue.getID());
    }

  }

  /**
   * Selects the previous available address entry.
   */
  public void selectPreviousEntry() {
    Selection currentSelection = getCurrentSelection();
    net.suberic.pooka.AddressMatcher matcher = getNewMessageUI().getSelectedProfile().getAddressMatcher();
    if (matcher != null) {
      AddressBookEntry newValue = matcher.getPreviousMatch(currentSelection.text);
      if (newValue != null) {
  replaceAddressText(currentSelection, newValue.getID());
      }
    }
  }

  /**
   * Adds the given AddressBookEntries to the address field.
   */
  public void addAddresses(AddressBookEntry[] newEntries) {
    // first see if we're actually adding anything
    if (newEntries == null || newEntries.length < 1)
      return;

    // next see if we need to add a comma.
    String currentValue = getText();
    boolean addComma = false;
    boolean found = false;
    for (int i = currentValue.length() - 1; !found && i >=0; i--) {
      char currentChar = currentValue.charAt(i);
      if (! Character.isWhitespace(currentChar)) {
  found = true;
  if (currentChar != ',')
    addComma = true;
      }
    }

    StringBuffer newValue = new StringBuffer(currentValue);
    if (addComma)
      newValue.append(", ");

    for (int i = 0; i < newEntries.length; i++) {
      newValue.append(newEntries[i].getAddressString());
      if (i < newEntries.length -1)
  newValue.append(", ");
    }
   
    setText(newValue.toString());
  }

  static java.util.HashMap buttonImageMap = new java.util.HashMap();

  /**
   * Creates a button that pulls up an editor dialog for addresses.
   */
  public JButton createAddressButton(int width, int height) {
    java.awt.Dimension key = new java.awt.Dimension(width, height);
    java.awt.Image defaultImage = (java.awt.Image) buttonImageMap.get(key);
    if (defaultImage == null) {
      ImageIcon addressIcon = Pooka.getUIFactory().getIconManager().getIcon(Pooka.getProperty("AddressBook.button", "Book"));
     
      if (addressIcon != null) {
  java.awt.Image addressImage = addressIcon.getImage();
  defaultImage = addressImage.getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH);
  addressIcon.setImage(defaultImage);
  buttonImageMap.put(key, defaultImage);
      }
    }

    JButton returnValue = null;
    if (defaultImage != null) {
      ImageIcon addressIcon = new ImageIcon(defaultImage);
      returnValue = new JButton(addressIcon);
    } else {
      returnValue=new JButton();
    }
    returnValue.setFocusable(false);
    returnValue.addActionListener(new AbstractAction() {
  public void actionPerformed(java.awt.event.ActionEvent e) {
    getNewMessageUI().showAddressWindow(AddressEntryTextArea.this);
  }
      });

    returnValue.setToolTipText(Pooka.getProperty("AddressBookEditor.buttonText", "Open Address Book"));
    returnValue.setMargin(new java.awt.Insets(1,1,1,1));
    returnValue.setSize(width, height);
    return returnValue;
  }

  /**
   * Returns the parent MessageUI.
   */
  public NewMessageUI getNewMessageUI() {
    return (NewMessageUI) messageProxy.getMessageUI();
  }

  private class Selection {
    int beginOffset;
    int endOffset;
    String text;
    SelectionStatus status = null;

    Selection(int newBegin, int newEnd, String newText) {
      beginOffset = newBegin;
      endOffset = newEnd;
      text = newText;
    }
  }

  private class SelectionStatus {
    String addressText = null;
    int status;
   
    SelectionStatus(String newAddressText, int newStatus) {
      addressText = newAddressText;
      status = newStatus;
    }
  }
  //----------- focus listener ----------------
  /**
   * a no-op -- don't do anything on focusGained.
   */
  public void focusGained(java.awt.event.FocusEvent e) {
   
  }

  /**
   *
   */
  public void focusLost(java.awt.event.FocusEvent e) {
    lastMatchedTime = System.currentTimeMillis();
  }

  //----------- updater thread ----------------

  static synchronized void createUpdateThread() {
    if (updateThread == null) {
      updateThread = new Thread(new Updater(), "AddressEntryTextArea - Update Thread");
      updateThread.start();
    }
  }

  static class Updater implements Runnable {

    long sleepTime = 60000;

    Updater() {
    }

    public void run() {
      sleepTime = 0;
      java.util.Set entrySet = areaList.entrySet();
      while(! entrySet.isEmpty()) {
  sleepTime = 60000;
  java.util.Iterator entryIter = entrySet.iterator();
  while (entryIter.hasNext()) {
    long currentTime = System.currentTimeMillis();
    AddressEntryTextArea area = (AddressEntryTextArea) ((java.util.Map.Entry)entryIter.next()).getKey();
    if (area.lastKeyTime > area.lastMatchedTime) {
      if (area.lastKeyTime + area.delayInMilliSeconds < currentTime) {
        if (area.completeNow || area.automaticallyDisplay) {
    area.completeNow = false;
    area.updateTextValue();
        }

        area.updateAddressList();

      } else {
        sleepTime = Math.min(sleepTime, (area.delayInMilliSeconds + area.lastKeyTime) - currentTime);
      }
    }
  }

  try {
    Thread.currentThread().sleep(sleepTime);
  } catch (InterruptedException e) {
  }
      }
    }
  }

}
TOP

Related Classes of net.suberic.pooka.gui.AddressEntryTextArea

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.