/* -*- 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 <talisman@anamorphic.com>
 *               Edwin Woudt <edwin@woudt.nl>
 */

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());
        }
    }
}