/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Mozilla Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Grendel mail/news client. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are Copyright (C) 1997 * Netscape Communications Corporation. All Rights Reserved. * * Contributors: Jeff Galyan * Edwin Woudt */ package grendel.composition; /* Gadget organization: an AddressList has one AddressPanel. an AddressPanel has many AddressLine(s). an AddressLine has one DeliveryButton, one AddressIcon and one AddressTextField. */ import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.JTable; //import netscape.orion.misc.*; public class AddressList extends JScrollPane implements Serializable { protected AddressPanel mAddressPanel; public AddressList() { super(); //scroll panel // JViewport spViewPort = getViewport(); JViewport spViewPort = new JViewport(); // System.out.println(spViewPort.toString()); //create addressList panel mAddressPanel = new AddressPanel (); //add address list panel to scroll panel. spViewPort.add(mAddressPanel); spViewPort.setView(mAddressPanel); setViewport(spViewPort); setBackground (Color.white); } int mProp1 = 0; public void setProp1 (int aVal) { mProp1 = aVal; } public int getProp1 () { return mProp1; } int mProp2 = 0; public void setProp2 (int aVal) { mProp2 = aVal; } public int getProp2 () { return mProp2; } /** * Clears and sets the addresses from an Array. * @param aAddresses An array of addresses. * @see getAddresses() */ public void setAddresses (Addressee[] aAddresses) { mAddressPanel.removeAllAddressLines (); //delete all entries. if (null != aAddresses) { for (int i = 0; i < aAddresses.length; i++) { if (null != aAddresses[i]) mAddressPanel.addAddresseLine (aAddresses[i]); } } mAddressPanel.repack(); } /** */ public Dimension getPreferredSize() { return mAddressPanel.getPreferredSize(); } /** * Returns the addresses in the form of an array. * @returns An array of addresses. * @see setAddresses() */ public Addressee[] getAddresses () { Vector temp = mAddressPanel.mAddressees; int i; //reject blank entries. for (i = 0; i < temp.size(); i++) { AddressLine al = (AddressLine) temp.elementAt(i); if (al.isBlank()) { temp.removeElement (al); } } //copy over from vector into array. Addressee[] anArray = new Addressee[temp.size()]; for (i = 0; i < temp.size(); i++) { anArray[i] = ((AddressLine) temp.elementAt(i)).getAddressee(); } return anArray; } /** * */ public class AddressPanel extends JPanel implements KeyListener { protected Vector mAddressees; //Addresses private Dimension mAddLineSize; //The size of one AddressLine. For layout. private Dimension mPerfSize; //The preferred size of this panel = 4 * mAddLineSize. public AddressPanel () { super(); this.setLayout (null); //vector for holding addressee list. mAddressees = new Vector(); //create the first addressee to start them off. repack(); } /** * layout Addressee lines. */ public void doLayout() { int i; Enumeration e; Dimension size = getSize(); /*place each addressee line in one column multiple rows. +---------------+ | addressee #1 | |---------------| | addressee #2 | |---------------| | ... | |---------------| | addressee #n | +---------------+ */ for (e = mAddressees.elements(), i = 0; e.hasMoreElements(); i++) { AddressLine al = (AddressLine) e.nextElement(); al.setBounds(0, i * mAddLineSize.height, size.width, mAddLineSize.height); } } // public Dimension getMaximumSize() { // return getPreferredSize(); // } // public Dimension getMinimumSize() { // return getPreferredSize(); // } public void addNotify () { super.addNotify (); //store the first AddressLine's size for layout. AddressLine al = (AddressLine) mAddressees.elementAt(0); mAddLineSize = al.getPreferredSize(); //the preferred size of the panel is 4 AddressLines tall and full parent width. mPerfSize = new Dimension (mAddLineSize.width, 5 * mAddLineSize.height); } /** * Returns the preferred size. * The preferred size of the panel is 4 AddressLines tall and full parent width. * @return */ public Dimension getPreferredSize() { //the preferred size of the panel is 4 AddressLines tall and full parent width. return (mPerfSize); } /** * Adds a new address line to the list. * @param aAddressee the Addresee you which to add. * @see removeAddressLine */ private synchronized void addAddresseLine (Addressee aAddressee) { //create the new AddressLine. AddressLine al = new AddressLine (aAddressee); //Add a keyboard listener for navigation //so they may navigate BETWEEN AddressLines using arrow keys. al.atfAddKeyListener (this); add (al); //add AddressLine gadget to panel mAddressees.addElement (al); //remember in vector table validate(); //layout control again (scrollbar may appear). } /** * Remove any blank lines from the middle * and appends a single blank line to the list. */ private void repack () { //remove blank lines for (int i = 0; i < mAddressees.size() - 2; i++) { AddressLine al = (AddressLine) mAddressees.elementAt(i); if (al.isBlank()) { removeAddressLine(al); } } //no entries at all so add a blank line. if (0 == mAddressees.size()) { addAddresseLine (new Addressee ("", Addressee.TO)); } else { //add a blank last line if needed. AddressLine lastAL = (AddressLine) mAddressees.elementAt(mAddressees.size() - 1); if (!lastAL.isBlank()) { addAddresseLine (new Addressee ("", lastAL.getDeliveryMode())); } } } /* * Place focus on the last addressLine. */ private void focusOnLast () { int last = mAddressees.size() - 1; //get its TextFiled and set focus on it. if (last > -1) { AddressLine al = (AddressLine) mAddressees.elementAt(last); al.atfRequestFocus(); } } /** * Removes an address line from the list. * @param aAddressLine The address line you wish to remove. * @see addAddresseLine */ private synchronized void removeAddressLine (AddressLine aAddressLine) { remove (aAddressLine); //remove AddressLine gadget from panel. aAddressLine.atfRemoveKeyListener (this); //stop listening for key events. mAddressees.removeElement (aAddressLine); //remove from vector table. validate(); //layout control again (scrollbar). repaint(); } /** * Removes all addressee lines. * @see removeAddressLine * @see addAddresseLine */ private synchronized void removeAllAddressLines () { removeAll(); //remove all AddressLines gadget from panel. mAddressees.removeAllElements(); //clear vector table. validate(); //layout control again (scrollbar). repaint(); } /** * Resonds to keyboard events for navigation (up, down, enter, etc.) * @see addAddresseLine */ //implements KeyListener... public void keyReleased (KeyEvent e) {} public void keyTyped (KeyEvent e) {} /* Recieve keyPressed events for this->AddressLine->AddressTextField. KeyEvent response table KEY_PRESSED Old Position State New Positon Behavior --------------------------------------------------------------------------------------- VK_UP NOT first - Previous VK_UP First - First VK_DOWN NOT last - Next VK_DOWN Last - Last VK_BACK_SPACE First line empty First VK_BACK_SPACE NOT first line empty Previous Delete this address line. VK_DELETE Last line empty Last VK_DELETE NOT last line empty Same Delete this address line. */ public void keyPressed (KeyEvent e) { //filter out only the keys we're interested in. if ((KeyEvent.KEY_PRESSED == e.getID()) && ( (KeyEvent.VK_UP == e.getKeyCode()) || (KeyEvent.VK_BACK_SPACE == e.getKeyCode()) || (KeyEvent.VK_DELETE == e.getKeyCode()) || (KeyEvent.VK_DOWN == e.getKeyCode()) ) ) { //find out which AddressLine created this key press. Component sourceComp = e.getComponent(); //check that this is an AddressTextField. if (sourceComp instanceof AddressTextField) { AddressTextField sourceATF = (AddressTextField)sourceComp; //get its parent AddressLine gadget. AddressLine sourceAL = (AddressLine) sourceATF.getParent(); //locate the AddressLine in the vector table mAddressees. int lastIndex = mAddressees.lastIndexOf(sourceAL); // This should never happen. // (i.e. You should never get a KeyEvent from a gadget not in the mAddressees Vector) // FIX: do something. if (-1 == lastIndex) { //System.out.println ("Internal error."); } else { //calculate where to send focus depending on the keypress. int incFocus = 0; //increment focus (-1, 0, +1) //UP if (KeyEvent.VK_UP == e.getKeyCode()) { incFocus = -1; //focus previous. } //DOWN else if (KeyEvent.VK_DOWN == e.getKeyCode()) { incFocus = +1; //focus next. } //BACKSPACE or DELETE else if ((KeyEvent.VK_BACK_SPACE == e.getKeyCode()) || (KeyEvent.VK_DELETE == e.getKeyCode())) { //if they've pressed backspace or delete and this address line // is already empty them delete this AddressLine. //no matter what, if there is only one AddressLine left // then don't delete it or try to change focus. if (1 >= mAddressees.size()) return; //Check to see if the current AddressTextField (ATF) is empty. // If so then delete it. if (sourceAL.isBlank()) { //remove the AddressLine from this container and vector list. removeAddressLine (sourceAL); if (KeyEvent.VK_BACK_SPACE == e.getKeyCode()) incFocus = -1; //focus previous. else //KeyEvent.VK_DELETE == e.getKeyCode() incFocus = 0; //stay where you are. } } //set focus to the next AddressTextField. { int nextIndex = lastIndex + incFocus; if (nextIndex < 0) { nextIndex = 0; } else if (nextIndex > (mAddressees.size() - 1)) { nextIndex = mAddressees.size() - 1; } //get its TextFiled and set focus on it. AddressLine nextAL = (AddressLine) mAddressees.elementAt(nextIndex); nextAL.atfRequestFocus(); } } } } } public void paint(Graphics g) { //paint the AddressLine gadets. super.paint(g); Dimension size = getSize(); g.setColor (Color.blue); //draw horizonttal lines BELOW the AddressLine gadgets. for (int i = mAddressees.size() * mAddLineSize.height; i < size.height; i += mAddLineSize.height) { g.drawLine (0, i, size.width, i); } //draw Vertical line BELOW the AddressLine gadgets and lined up with the left side addressee button. if (0 < mAddressees.size()) { AddressLine firstAddressLine = (AddressLine) mAddressees.elementAt(0); int buttonWidth = firstAddressLine.getButtonWidth(); g.drawLine (buttonWidth, mAddressees.size() * mAddLineSize.height, buttonWidth, size.height); } } } //************************* /** * An AddressLine has one DeliveryButton and one AddressTextField. */ public class AddressLine extends JPanel { private DeliveryButton mDeliveryButton; private AddressTextField mAddressTextField; private DragIcon mDragIcon; private boolean mShowDeliveryButton = true; public AddressLine (Addressee aAddressee) { super(); this.setLayout (null); //see doLayout. //left side delivery button ("To:") mDeliveryButton = new DeliveryButton (aAddressee.getDelivery()); add (mDeliveryButton); //center DragIcon mDragIcon = new DragIcon(); add (mDragIcon); //right side text field ("john_doe@company.com") mAddressTextField = new AddressTextField (aAddressee.getText(), mDeliveryButton); add (mAddressTextField); } protected void atfRemoveKeyListener(KeyListener kl){ mAddressTextField.removeKeyListener (kl); } protected void atfAddKeyListener(KeyListener kl) { mAddressTextField.addKeyListener (kl); } protected void atfRequestFocus() { mAddressTextField.requestFocus(); } protected int getButtonWidth() { return mDeliveryButton.getPreferredSize ().width; } protected boolean isBlank() { return mAddressTextField.getText().equals (""); } protected int getDeliveryMode() { return mDeliveryButton.getDeliveryMode (); } /** * layout Delivery Button, DragIcon and AddressTextField. */ public void doLayout() { /* Layout +----------------+----------+---------------------+ | DeliveryButton | DragIcon | AddressTextField >>>| +----------------+----------+---------------------+ */ Dimension mySize = getSize(); //get this containers size. //DeliveryButton Dimension dbSize = mDeliveryButton.getPreferredSize (); mDeliveryButton.setBounds(0, 0, dbSize.width, mySize.height); int x = dbSize.width; //DragIcon Dimension diSize = mDragIcon.getPreferredSize (); mDragIcon.setBounds(x, 0, diSize.width, mySize.height); x += diSize.width; //AddressTetField takes up the rest. Dimension atfSize = mAddressTextField.getPreferredSize (); mAddressTextField.setBounds(x, (mySize.height - atfSize.height)/2, mySize.width - x, mySize.height); } /* */ public Dimension getPreferredSize() { return mDeliveryButton.getPreferredSize (); } /* * Returns an Addressee for the line. */ protected Addressee getAddressee() { return (new Addressee (mAddressTextField.getText(), mDeliveryButton.getDeliveryMode ())); } /** * Paint the blue lines in the background. */ public void paint(Graphics g) { super.paint(g); Dimension size = getSize(); FontMetrics fm = g.getFontMetrics(); int buttonWidth = getButtonWidth(); g.setColor (Color.blue); g.drawLine (buttonWidth, 0, size.width, 0); //top g.drawLine (buttonWidth, size.height, size.width, size.height); //bottom } } //************************* /** * Image icon for drag and drop. */ public class DragIcon extends JPanel { private ImageIcon mIcon; public DragIcon () { super(); setBorder(BorderFactory.createEmptyBorder (5, 8, 5, 8)); //create image icon for drag and drop. mIcon = new ImageIcon(getClass().getResource("images/card.gif")); } public void paint (Graphics g) { Dimension size = getSize(); //try to center the icon. int x = (size.width - mIcon.getIconWidth())/2; x = (x < 0) ? 0 : x; int y = (size.height - mIcon.getIconHeight())/2; y = (y < 0) ? 0 : y; mIcon.paintIcon (this, g, x, y); } } public class AddressTextField extends JTextField implements FocusListener { // public class AddressTextField extends ATC_Field implements FocusListener { private final String ADDRESS_SEPERATORS = ","; private final String ADDRESS_QUOTES = "\""; private DeliveryButton mDeliveryButton; public AddressTextField (String aString, DeliveryButton aDeliveryButton) { super(aString); // super (aString, new TestDataSource2()); mDeliveryButton = aDeliveryButton; //red completion text. // setCompletionColor (Color.red); //NO border. setBorder(null); //get focus gained/lost events. addFocusListener(this); //catch tabs and enters before anyone else. enableEvents (AWTEvent.KEY_EVENT_MASK); //see processKeyEvent } public void setCompletionColor(Color c) { } /** * catch tabs before anyone else. */ public void processKeyEvent (KeyEvent e) { //TAB if ('\t' == e.getKeyChar()) return; //ignore tab characters. //ENTER if ('\n' == e.getKeyChar()) { evaluate (); //evaluate this line for mutilple entries. mAddressPanel.focusOnLast(); //Put focus on the last blank entry. } super.processKeyEvent(e); } /** * stub */ public void focusGained(FocusEvent evt) { } /** * On focusLost, evaluate line for multiple entries. */ public void focusLost(FocusEvent evt) { evaluate (); } /** * Evaluate line for multiple addresses, notify parent to add the new entries. */ private void evaluate() { String [] tokens = parseLine (getText()); //we've lost focus and they type nothing or a bunch of ADDRESS_SEPERATORS on this line. if (tokens.length == 0) { setText (""); } //else they typed something.... else { //we keep the first. setText (tokens[0]); //if more than one address is on this line then add the others. if (tokens.length > 1) { for (int i = 1; i < tokens.length; i++) { mAddressPanel.addAddresseLine (new Addressee (tokens[i], mDeliveryButton.getDeliveryMode())); } } } //repack the lines. (remove blanks) mAddressPanel.repack(); } /** * Parses up the string. * @param aString The String to parse. * @return returns an array of strings. * @see ADDRESS_SEPERATORS * @see ADDRESS_QUOTES */ private String[] parseLine (String aString) { Vector tokenVec = new Vector (); boolean quoted = false; int tail = 0; int head = 0; //step through each character in the string. for (head = 0; head < aString.length(); head++) { //is this a quote character? if (-1 != ADDRESS_QUOTES.indexOf(aString.charAt(head))) { //are we already in a quoted string? if (quoted) { String token = aString.substring(tail, head + 1).trim(); //if this is not a blank then increment count. if (!token.equals("")) tokenVec.addElement(token); tail = head + 1; quoted = false; //quoting off } else { tail = head; //remember the quote char. quoted = true; //quoting on. } } //is this a seperator character? else if (-1 != ADDRESS_SEPERATORS.indexOf(aString.charAt(head))) { //if not in a quote and not if (!quoted) { String token = aString.substring(tail, head).trim(); //if this is not a blank then increment count. if (!token.equals("")) tokenVec.addElement(token); tail = head + 1; } } } //last token String quotingChar = ""; //did we finish with an open quote? if (quoted) { quotingChar = aString.substring(tail, tail + 1); //add a matching quote. } String token = aString.substring(tail, head).trim(); //if this is not a blank then increment count. if (!token.equals("")) tokenVec.addElement(token + quotingChar); //return an array of Strings. String [] tokenArray = new String [tokenVec.size()]; tokenVec.copyInto(tokenArray); return tokenArray; } } //************************* /** * DeliveryButton displays "To:", "Cc:", etc and has a popup menu to change values. */ public class DeliveryButton extends JPanel implements MouseListener, //show popup menu on mouse click FocusListener, //display focus when tabbed to. ActionListener, //get popup menu selection. KeyListener { //show popup menu when focus and spacebar pressed. private int mDeliveryMode; // private Dimension mPerfSize = null; private Dimension mPerfSize = new Dimension(50, 20); private Insets mInsets = new Insets (4, 4, 4, 8); private PopupMenu mPopup; public DeliveryButton (int aDeliveryMode) { //get your own mouse events for popup menu. addMouseListener (this); //get your own focus events for button highlight. addFocusListener (this); //display popup menu when in focus and spacebar pressed. addKeyListener (this); //create the popup menu.. mPopup = createPopup (); //add popup to me. add (mPopup); setDeliveryMode (aDeliveryMode); } /** * Creates the popup menu. */ private PopupMenu createPopup () { PopupMenu pm = new PopupMenu (Addressee.getDeliveryTitle()); //added text commands to popup for (int i = 0; i < Addressee.mDeliveryStr.length; i++) { MenuItem mi = new MenuItem (Addressee.mDeliveryStr[i]); pm.add (mi); mi.addActionListener (this); } return pm; } /* * Set the button delivery mode. * @param aDeliveryMode a value like Addressee.TO or Addressee.BCC * @see getDeliveryMode */ protected void setDeliveryMode (int aDeliveryMode) { mDeliveryMode = aDeliveryMode; repaint(); } /* * Return the button delivery mode. * @return a delivery mode a value like Addressee.TO or Addressee.BCC * @see setDeliveryMode */ protected int getDeliveryMode () { return mDeliveryMode; } public Dimension getMaximumSize() { return getPreferredSize(); } public Dimension getMinimumSize() { return getPreferredSize(); } /** * addNotify creates the preferred size dimension because only * after the peer has been created can we get the font metrics. */ public void addNotify () { super.addNotify (); if (null == mPerfSize) { Font fnt = getFont(); if (null != fnt) { FontMetrics fm = getToolkit().getFontMetrics(fnt); if (null != fm) { String longestString = Addressee.getLongestString(); mPerfSize = new Dimension(fm.stringWidth(longestString) + mInsets.left + mInsets.right, fm.getMaxAscent() + fm.getMaxDescent() + mInsets.top + mInsets.bottom); } } } } /** * Return PerfSize created in addNotify. */ public Dimension getPreferredSize() { if (null != mPerfSize) { return mPerfSize; } return new Dimension(50, 20); } public boolean isFocusTraversable() { return true; } /** * Display button highlight. */ public void focusGained(FocusEvent evt) { repaint(); } /** * Remove button highlight. */ public void focusLost(FocusEvent evt) { repaint(); } /** * Paint the button's bevels and arrow and text string (i.e. "To:") */ public void paint(Graphics g) { Dimension size = getSize(); //gray background g.setColor (Color.lightGray); g.fillRect (0, 0, size.width - 1, size.height - 1); //draw bezel borders g.setColor (Color.white); g.drawLine (0, 0, 0, size.height - 1); //top g.drawLine (0, 0, size.width - 1, 0); //left g.setColor (Color.black); g.drawLine (size.width - 1, 0, size.width - 1, size.height - 1); //right g.drawLine (0, size.height - 1, size.width - 1, size.height - 1); //bottom //down arrow g.setColor (Color.gray); int xPoints[] = {0, 10, 5}; int yPoints[] = {0, 0, 10}; Polygon arrow = new Polygon (xPoints, yPoints, 3); Rectangle arrowRect = arrow.getBounds(); int deltaX = mInsets.left; int deltaY = (size.height - arrowRect.height)/2; arrow.translate (deltaX, deltaY); g.fillPolygon(arrow); g.setColor (Color.white); g.drawLine (xPoints[1] + deltaX, yPoints[1] + deltaY, xPoints[2] + deltaX, yPoints[2] + deltaY); //right edge //Delivery mode text (ie. "To:") g.setColor (Color.black); FontMetrics fm = g.getFontMetrics(); //Font height is wacky. //g.drawString (mLabel, arrowRect.width + mInsets.left + 4, (fm.getHeight() + size.height)/2); g.drawString (Addressee.deliveryToString(mDeliveryMode), arrowRect.width + mInsets.left + 4, fm.getHeight()); //draw focus rectangle (AWT 1.1 doesn't have support for line styles) if (true == hasFocus()) { final int offset = 3; //insets of focus rectangle. //top and bottom int bottomOffset = size.height - offset - 1; for (int x = offset; x < size.width - offset; x += 2) { g.drawLine (x, offset, x, offset); //top g.drawLine (x, bottomOffset, x, bottomOffset); //bottom } //left and right int rightOffset = size.width - offset - 1; for (int y = offset; y < size.height - offset; y += 2) { g.drawLine (offset, y, offset, y); //left g.drawLine (rightOffset, y, rightOffset, y); //right } } } //implements KeyListener... public void keyReleased (KeyEvent e) {} public void keyTyped (KeyEvent e) {} /* * Display popup menu when spacebar pressed. */ public void keyPressed (KeyEvent e) { if ((KeyEvent.KEY_PRESSED == e.getID()) && (KeyEvent.VK_SPACE == e.getKeyCode())) { mPopup.show (e.getComponent (), 0, 0); } } /** * Called when popup menu item is selected. */ public void actionPerformed (ActionEvent e) { String menuCommand = e.getActionCommand(); setDeliveryMode (Addressee.deliveryToInt (menuCommand)); } public void mouseEntered (MouseEvent e) {} public void mouseExited (MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseClicked (MouseEvent e) {} /** * On any mouse click show the popup menu. * Windows has bug for getX(), getY(). (UNIX OK) */ public void mousePressed (MouseEvent e) { mPopup.show (e.getComponent (), e.getX(), e.getY()); } } }