/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (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.
 *
 * Contributor(s): 
 *
 * Created: Will Scullin <scullin@netscape.com>, 20 Oct 1997.
 *
 * Contributors: Jeff Galyan <jeffrey.galyan@sun.com>
 *               Giao Nguyen <grail@cafebabe.org>
 *               Edwin Woudt <edwin@woudt.nl>
 *               Thomas Down <thomas.down@tri.ox.ac.uk>
 *               Brian Duff <Brian.Duff@oracle.com>
 */

package grendel.composition;

import calypso.util.ByteBuf;
import calypso.util.ByteLineBuffer;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.io.*;
import java.net.URL;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Properties;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextArea;
import javax.swing.border.BevelBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.EventListenerList;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.TextAction;

import javax.activation.*;

import javax.mail.Address;
import javax.mail.Session;
import javax.mail.Message;
import javax.mail.Transport;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeUtility;
import javax.mail.internet.InternetAddress;

import grendel.prefs.base.GeneralPrefs;
import grendel.prefs.base.IdentityArray;
import grendel.prefs.base.IdentityStructure;
import grendel.storage.MessageExtra;
import grendel.storage.MessageExtraFactory;
import grendel.ui.ActionFactory;
import grendel.ui.GeneralPanel;
import grendel.ui.UIAction;
import grendel.widgets.CollapsiblePanel;
import grendel.widgets.GrendelToolBar;

public class CompositionPanel extends GeneralPanel {
  private Hashtable       mCommands;
  private Hashtable       mMenuItems;

  private AddressBar      mAddressBar;
  private AddressList     mAddressList;
  private AttachmentsList mAttachmentsList;
  private JTextComponent  mEditor;
  private JTextField      mSubject;
  private Session         mSession;
  private String          mMailServerHostName;
  private String          mMailUserName;
  private Message referredMsg;

  private EventListenerList mListeners = new EventListenerList();

  /**
   *
   */
  public CompositionPanel(Session aSession) {
    fResourceBase = "grendel.composition";

    mSession = aSession;

    //Create message panel
    //  The MessagePanel is...
    //      1) The subject JLabel
    //      2) The subject JTextField and
    //      3) An editor (JTextArea)
    //  The editor in the message panel must be created first so
    //  the supported JActions can be registered with the menu "mMenubar".
    MessagePanel messagePanel = new MessagePanel ();
    mEditor = messagePanel.getEditor ();
    mSubject = messagePanel.getSubject ();

    //toolbar buttons
    fToolBar = createToolbar();
    mAddressBar = new AddressBar(this);
    mAddressList = mAddressBar.getAddressList();
    mAttachmentsList = mAddressBar.getAttachmentsList();

    //the splitPane holds collapseble panels and message panels.
//         JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, false, collapsePanel, messagePanel);


    add(messagePanel, BorderLayout.CENTER);
  }

  public void dispose() {
  }

  /**
   * Fetch the list of actions supported by this
   * editor.  It is implemented to return the list
   * of actions supported by the embedded JTextComponent
   * augmented with the actions defined locally.
   */
  public UIAction[] getActions() {
    return defaultActions;
    // XXX WHS need to translate Actions to UICmds
    // return TextAction.augmentList(mEditor.getActions(), defaultActions);
  }

  /**
   * Return the addressing bar
   */

  public AddressBar getAddressBar() {
    return mAddressBar;
  }

  void setSubject(String subject) {
    mSubject.setText(subject);
  }

  String getSubject() {
    return mSubject.getText();
  }

  void setReferredMessage(Message m) {
    referredMsg = m;
  }

  Message getReferredMessage() {
    return referredMsg;
  }

  /**
   * Add a CompositionPanelListener
   */

  public void addCompositionPanelListener(CompositionPanelListener l) {
    mListeners.add(CompositionPanelListener.class, l);
  }
  
  /**
   * Remove a CompositionPanelListener
   */

  public void removeCompositionPanelListener(CompositionPanelListener l) {
    mListeners.remove(CompositionPanelListener.class, l);
  }

  /**
   * Quote the orgininal message in a reply
   */
   
  public void QuoteOriginalMessage() {
    QuoteOriginalText qot = new QuoteOriginalText();
    qot.actionPerformed(new ActionEvent(this,0,""));
  }

  /**
   * Inline the orgininal message when forwarding
   */
   
  public void InlineOriginalMessage() {
    InlineOriginalText iot = new InlineOriginalText();
    iot.actionPerformed(new ActionEvent(this,0,""));
  }

  /**
   * Add a signature
   */
   
  public void AddSignature() {
    AddSignatureAction asa = new AddSignatureAction();
    asa.actionPerformed(new ActionEvent(this,0,""));
  }

  protected void notifySendingMail() {
    Object[] listeners = mListeners.getListenerList();
    ChangeEvent changeEvent = null;
    for (int i = 0; i < listeners.length - 1; i += 2) {
      if (listeners[i] == CompositionPanelListener.class) {
        // Lazily create the event:
        if (changeEvent == null)
          changeEvent = new ChangeEvent(this);
        ((CompositionPanelListener)listeners[i+1]).sendingMail(changeEvent);
      }
    }
  }

  protected void notifyMailSent() {
    Object[] listeners = mListeners.getListenerList();
    ChangeEvent changeEvent = null;
    for (int i = 0; i < listeners.length - 1; i += 2) {
      if (listeners[i] == CompositionPanelListener.class) {
        // Lazily create the event:
        if (changeEvent == null)
          changeEvent = new ChangeEvent(this);
        ((CompositionPanelListener)listeners[i+1]).doneSendingMail(changeEvent);
      }
    }
  }

  protected void notifySendFailed() {
    Object[] listeners = mListeners.getListenerList();
    ChangeEvent changeEvent = null;
    for (int i = 0; i < listeners.length - 1; i += 2) {
      if (listeners[i] == CompositionPanelListener.class) {
        // Lazily create the event:
        if (changeEvent == null)
          changeEvent = new ChangeEvent(this);
        ((CompositionPanelListener)listeners[i+1]).sendFailed(changeEvent);
      }
    }
  }

  /**
   * Find the hosting frame, for the file-chooser dialog.
   */
  protected Frame getParentFrame() {
    for (Container p = getParent(); p != null; p = p.getParent()) {
      if (p instanceof Frame) {
        return (Frame) p;
      }
    }
    return null;
  }

  //"File" actions
  public static final String saveDraftTag             ="saveDraft";
  public static final String saveAsTag                ="saveAs";
  public static final String sendNowTag               ="sendNow";
  public static final String sendLaterTag             ="sendLater";
  public static final String quoteOriginalTextTag     ="quoteOriginalText";
  public static final String inlineOriginalTextTag    ="inlineOriginalText";
  public static final String addSignatureTag          ="addSignatureAction";
  public static final String selectAddressesTag       ="selectAddresses";
  public static final String goOfflineTag             ="goOffline";
  public static final String closeWindowTag           ="closeWindow";

  // "file->new" actions
  public static final String navigatorWindowTag       ="navigatorWindow";
  public static final String messageTag               ="message";
  public static final String blankPageTag             ="blankPage";
  public static final String pageFromTemplateTag      ="pageFromTemplate";
  public static final String pageFromWizardTag        ="pageFromWizard";

  // "file->attach" actions
  public static final String fileTag                  ="attachFile";
  public static final String webPageTag               ="webPage";
  public static final String addressBookCardTag       ="addressBookCard";
  public static final String myAddressBookCardTag     ="myAddressBookCard";

  //"Edit" actions
  public static final String undoTag                  ="undo";
  public static final String pasteAsQuotationTag      ="pasteAsQuotation";
  public static final String DeleteTag                ="delete";
  public static final String selectAllTag             ="selectAll";
  public static final String findInMessageTag         ="findInMessage";
  public static final String findAgainTag             ="findAgain";
  public static final String searchDirectoryTag       ="searchDirectory";
  public static final String preferencesTag           ="appPrefs";

  //"View" actions
  public static final String hideMessageToolbarTag    ="hideMessageToolbar";
  public static final String hideAddressingAreaTag    ="hideAddressingArea";
  public static final String viewAddressTag           ="viewAddress";
  public static final String viewAttachmentsTag       ="viewAttachments";
  public static final String viewOptionsTag           ="viewOptions";
  public static final String wrapLongLinesTag         ="wrapLongLines";
  


  // --- action implementations -----------------------------------
  private UIAction[] defaultActions = {
    //"File" actions
//        new SaveDraft(),
    new SaveAs(),
    new SendNow(),
//        new SendLater(),
    new QuoteOriginalText(),
    new SelectAddresses(),
//        new GoOffline(),
    ActionFactory.GetExitAction(),

    // "file->new" actions
//        new NavigatorWindow(),
//        new Message(),
//        new BlankPage(),
//        new PageFromTemplate(),
//        new PageFromWizard(),

    // "file->attach" actions
    new AttachFile(),
//        new WebPage(),
//        new AddressBookCard(),
//        new MyAddressBookCard(),

    //"Edit" actions
//        new Undo(),
    //new Cut(),    Handled by editor.
    //new Copy(),   Handled by editor.
    //new Paste(),  Handled by editor.
    new PasteAsQuotation(),
//        new Delete(),
//        new FindInMessage(),
//        new FindAgain(),
//        new SelectAll(),
//        new SearchDirectory(),
    ActionFactory.GetPreferencesAction(),

    //"View" actions
//        new HideMessageToolbar(),
//        new HideAddressingArea(),
//        new WrapLongLines()
  };

  //-----------------------
  //"File" actions
  //-----------------------
  /**
   * Try to save the message to the "draft" mailbox.
   * @see SaveAs
   */
  class SaveDraft extends UIAction {
    SaveDraft() {
      super(saveDraftTag);
      this.setEnabled(true);
    }
    public void actionPerformed(ActionEvent e) {}
  }

  /**
   * Try to save the message to a text file.
   * @see SaveDraft
   */
  class SaveAs extends UIAction {
    SaveAs() {
      super(saveAsTag);
      this.setEnabled(true);
    }

    public void actionPerformed(ActionEvent e) {
      FileDialog fileDialog = new FileDialog (getParentFrame(), "Save As", FileDialog.SAVE);
      fileDialog.setFile("untitled.txt");
      fileDialog.show();  //blocks

      String fileName = fileDialog.getFile();
      //check for canel
      if (fileName == null) {
        return;
      }

      //try to retieve all text from message area.
      String messageText = mEditor.getText();

      //try to write text to file.
      try {
        //assemble fill path to file.
        String directory = fileDialog.getDirectory();
        File f = new File(directory, fileName);
        FileOutputStream fstrm = new FileOutputStream(f);

        //write text to the file.
        fstrm.write(messageText.getBytes()) ;
        fstrm.flush();
      } catch (IOException io) {
      }
    }
  }

  /**
   * Send the mail message now.
   */
  class SendNow extends UIAction {
    SendNow() {
      super(sendNowTag);
      this.setEnabled(true);
    }

    /**
     * Check if an array of bytes are in range 1-127 (i.e. clean ASCII)
     */

    public boolean isCleanText(byte [] data) {
      for (int i=0; i < data.length; ++i)
        if (data[i] <= 0)
          return false;

      return true;
    }

    public void actionPerformed(ActionEvent ae) {
      //try to retieve all text from message area.
      notifySendingMail();
      boolean success = false;

      String messageText = mEditor.getText();

      if (messageText == null) {
        System.err.println("****Null messageText");
      }

      //try to send the message
      //get the current list of recipients.
      Addressee[] recipients = mAddressList.getAddresses();

      //Check that is at least one recipient.
      if (0 < recipients.length) {

        //create a mime message
        MimeMessage msg = new MimeMessage(mSession); // TD

        try {
          IdentityStructure ident = IdentityArray.GetMaster().get(
                            mAddressBar.getOptionsPanel().getSelectedIdentity());
          
          //set who's sending this message.
          msg.setFrom (new InternetAddress(ident.getEMail(), ident.getName()));

          //add the recipients one at a time.
          for (int i = 0; i < recipients.length; i++) {
            javax.mail.Address[] toAddress = new InternetAddress[1];
            toAddress[0] = new InternetAddress(recipients[i].getText());

            Message.RecipientType deliverMode = Message.RecipientType.TO;

            //map grendel.composition.Addressee delivery modes
            //  into javax.mail.Message delivery modes.
            switch (recipients[i].getDelivery()) {
              case Addressee.TO:
                deliverMode = Message.RecipientType.TO;
                break;
              case Addressee.CC:
                deliverMode = Message.RecipientType.CC;
                break;
              case Addressee.BCC:
                deliverMode = Message.RecipientType.BCC;
                break;
            }
            msg.addRecipients(deliverMode, toAddress);
          }

          msg.setSubject(mSubject.getText());        //set subject from text
                                                     //field.
          msg.setHeader("X-Mailer", "Grendel [development version]");
                                                     //and proud of it! 
          msg.setSentDate(new java.util.Date());     //set date to now.

          String [] attachments = mAttachmentsList.getAttachments();
          if (attachments.length == 0) {
            msg.setContent(messageText, "text/plain"); //contents.
          } else {
            MimeMultipart multi = new MimeMultipart();
              
            MimeBodyPart mainText = new MimeBodyPart();
            mainText.setText(messageText);
            multi.addBodyPart(mainText);

            for (int i = 0; i < attachments.length; ++i) {
              try {
                File f = new File(attachments[i]);
                int len = (int) f.length();
                String mimeString =
                  FileTypeMap.getDefaultFileTypeMap().getContentType(f);
                MimeType mimeType = new MimeType(mimeString);

                byte [] bs = new byte[len];
                FileInputStream fis = new FileInputStream(f);
                DataInputStream dis = new DataInputStream(fis);
                dis.readFully(bs);
                dis.close();
                fis.close();

                MimeBodyPart att = new MimeBodyPart();
                String encName = "7bit";
                if (mimeType.getPrimaryType().equalsIgnoreCase("text")) {
                  if (!isCleanText(bs)) {
                    encName = "quoted-printable";
                  }
                } else {
                  encName = "base64";
                }

                att.setText(new String(bs)); 
                att.setHeader("Content-Type", mimeString);
                att.setHeader("Content-Transfer-Encoding", encName);
                att.setFileName(new File(attachments[i]).getName());
                att.setDisposition("Attachment");
                multi.addBodyPart(att);
              } catch (Exception e) {
                // Could be IOException or MessagingException.  For now...
                e.printStackTrace();
              }
            }

            msg.setContent(multi);
          }

          try {
            Properties props = mSession.getProperties();
            props.put("mail.host", GeneralPrefs.GetMaster().getSMTPServer());
            Session newSession = Session.getInstance(props,null);
            newSession.getTransport("smtp").send(msg);       // send the message.
          } catch (MessagingException exc) {
            exc.printStackTrace();
          }
                  
          success = true;
        } catch (javax.mail.SendFailedException sex) {
          sex.printStackTrace();
          Address addr[] = sex.getInvalidAddresses();
          if (addr != null) {
            System.err.println("Addresses: ");
            for (int i = 0; i < addr.length; i++) {
              System.err.println("  " + addr[i].toString());
            }
          }
        } catch (MessagingException mex) {
          mex.printStackTrace();
        } catch (UnsupportedEncodingException uee) {
          uee.printStackTrace();
        }
      }

      if (success) {
        //hide this frame after sending mail.
        notifyMailSent();
      } else {
        notifySendFailed();
      }
    }
  }

  /**
   * Quote the original text message into the editor.
   * @see PasteAsQuotation
   */
  class QuoteOriginalText extends UIAction {
    QuoteOriginalText() {
      super(quoteOriginalTextTag);
      this.setEnabled(true);
    }
    public void actionPerformed(ActionEvent event) {
      if (referredMsg == null) return; // Or beep or whine??? ###
      // ### Get the message as a stream of text.  This involves
      // complicated things like invoking the MIME parser, and throwing
      // away non-text parts, and translating HTML to text, and so on.
      // Yeah, right.
      InputStream plaintext = null;
      try {
        plaintext = referredMsg.getInputStream();
      } catch (IOException ioe) {
      } catch (MessagingException e) {
      }
      if (plaintext == null) return; // Or beep or whine??? ###


      int position = mEditor.getCaretPosition();
      Document doc = mEditor.getDocument();

      MessageExtra mextra = MessageExtraFactory.Get(referredMsg);
      String author;
      try {
        author = mextra.getAuthor();
      } catch (MessagingException e) {
        author = "???";         // I18N? ###
      }
      String tmp = author + " wrote:\n"; // I18N ###
      try {
        doc.insertString(position, tmp, null);
        position += tmp.length();
      } catch (BadLocationException e) {
      }

      // OK, now insert the data from the plaintext, and precede each
      // line with "> ".

      ByteLineBuffer linebuffer = new ByteLineBuffer();
      ByteBuf empty = new ByteBuf();
      linebuffer.setOutputEOL(empty);

      boolean eof = false;

      ByteBuf buf = new ByteBuf();
      ByteBuf line = new ByteBuf();

      while (!eof) {
        buf.setLength(0);
        try {
          eof = (buf.read(plaintext, 1024) < 0);
        } catch (IOException e) {
          eof = true;
        }
        if (eof) {
          linebuffer.pushEOF();
        } else {
          linebuffer.pushBytes(buf);
        }
        while (linebuffer.pullLine(line)) {
          try {
            doc.insertString(position, "> ", null);
            position += 2;
            doc.insertString(position, line.toString(), null);
            position += line.length();
            doc.insertString(position, "\n", null);
            position += 1;
          } catch (BadLocationException e) {
          }
        }
      }
      repaint();
      try {
        plaintext.close();
      } catch (IOException e) {
      }
    }
  }

  /**
   * Inline the original text message into the editor.
   * @see QuoteOriginalText
   */
  class InlineOriginalText extends UIAction {
    InlineOriginalText() {
      super(inlineOriginalTextTag);
      this.setEnabled(true);
    }
    public void actionPerformed(ActionEvent event) {
      if (referredMsg == null) return; // Or beep or whine??? ###
      // ### Get the message as a stream of text.  This involves
      // complicated things like invoking the MIME parser, and throwing
      // away non-text parts, and translating HTML to text, and so on.
      // Yeah, right.
      InputStream plaintext = null;
      try {
        plaintext = referredMsg.getInputStream();
      } catch (IOException ioe) {
      } catch (MessagingException e) {
      }
      if (plaintext == null) return; // Or beep or whine??? ###


      int position = mEditor.getCaretPosition();
      Document doc = mEditor.getDocument();

      MessageExtra mextra = MessageExtraFactory.Get(referredMsg);
      String author;
      try {
        author = mextra.getAuthor();
      } catch (MessagingException e) {
        author = "???";         // I18N? ###
      }
      String subject;
      try {
        subject = referredMsg.getSubject();
      } catch (MessagingException e) {
        subject = "???";         // I18N? ###
      }
      String tmp = "\n\n-------- Original Message --------\n";
      tmp = tmp + "From: " + author + "\n";                      // I18N ###
      tmp = tmp + "Subject: " + subject + "\n";
      tmp = tmp + "\n";
      try {
        doc.insertString(position, tmp, null);
        position += tmp.length();
      } catch (BadLocationException e) {
      }

      // OK, now insert the data from the plaintext, and precede each
      // line with "> ".

      ByteLineBuffer linebuffer = new ByteLineBuffer();
      ByteBuf empty = new ByteBuf();
      linebuffer.setOutputEOL(empty);

      boolean eof = false;

      ByteBuf buf = new ByteBuf();
      ByteBuf line = new ByteBuf();

      while (!eof) {
        buf.setLength(0);
        try {
          eof = (buf.read(plaintext, 1024) < 0);
        } catch (IOException e) {
          eof = true;
        }
        if (eof) {
          linebuffer.pushEOF();
        } else {
          linebuffer.pushBytes(buf);
        }
        while (linebuffer.pullLine(line)) {
          try {
            //doc.insertString(position, "> ", null);
            //position += 2;
            doc.insertString(position, line.toString(), null);
            position += line.length();
            doc.insertString(position, "\n", null);
            position += 1;
          } catch (BadLocationException e) {
          }
        }
      }
      repaint();
      try {
        plaintext.close();
      } catch (IOException e) {
      }
    }
  }

  /**
   * Add a signature
   */
  class AddSignatureAction extends UIAction {
    AddSignatureAction() {
      super(addSignatureTag);
      this.setEnabled(true);
    }
    public void actionPerformed(ActionEvent event) {

      Document doc = mEditor.getDocument();
      int oldPosition = mEditor.getCaretPosition();
      
      //remove the old signature
      for (int i=0; i<doc.getLength()-4;i++) {
        try {
          if (doc.getText(i,1).equals("\n")) {
            if (doc.getText(i+1,4).equals("-- \n")) {
              doc.remove(i, doc.getLength()-i);
            }
          }
        } catch (BadLocationException ble) {
          ble.printStackTrace();
        }
      }
      
      //the signature will be added at the end
      int position = doc.getEndPosition().getOffset() - 1;
      
      //compose the string including the separator
      String s = "\n-- \n";
      int ident = mAddressBar.getOptionsPanel().getSelectedIdentity();
      s = s + IdentityArray.GetMaster().get(ident).getSignature();

      //if a signature was specified, add it now
      if (s.length()>5) {
        try {
          doc.insertString(position, s, null);
        } catch (BadLocationException ble) {
          ble.printStackTrace();
        }
      }
            
      mEditor.setCaretPosition(oldPosition);
      
    }
  }

  class SelectAddresses extends UIAction {
    SelectAddresses() {
      super(selectAddressesTag);
      this.setEnabled(true);
    }

    public void actionPerformed(ActionEvent e) {
      AddressDialog aDialog = new AddressDialog(getParentFrame());
      Addressee[]     mAddresses;

      //get the current list to hand to the dialog.
      mAddresses = mAddressList.getAddresses ();

      //display the addressee dialog
      aDialog.setAddresses (mAddresses);      //initialize the dialog
      aDialog.show ();                        //blocks

      if (false == aDialog.getCanceled()) {
        //get the addresses from dialog
        mAddresses = aDialog.getAddresses();

        //update addresses panel with data from dialog.
        mAddressList.setAddresses (mAddresses);
      }

      aDialog.dispose();
    }
  }

  //-----------------------
  // "file->new" actions
  //-----------------------

  //-----------------------
  // "file->attach" actions
  //-----------------------
  class AttachFile extends UIAction {
    AttachFile() {
      super(fileTag);
      this.setEnabled(true);
    }

    //display the AttachmentsList and have the file dialog "popup" on them.
    public void actionPerformed(ActionEvent e) {
      //mAddressBar.setSelectedIndex(1);
      mAttachmentsList.showDialog();
    }
  }

  //-----------------------
  //"Edit" actions
  //-----------------------

  /**
   * Quote and paste whatever string that's on the clipboard into the editor.
   * @see QuoteOriginalText
   */
  class PasteAsQuotation extends UIAction {
    PasteAsQuotation() {
      super(pasteAsQuotationTag);
      this.setEnabled(true);
    }

    public void actionPerformed(ActionEvent e) {
      Clipboard board=Toolkit.getDefaultToolkit().getSystemClipboard();

      /* an instance of the system clipboard */
      Transferable transfer=board.getContents(this);

      // Try to get a string from the clipboard
      try {
        String clipboardData = (String)
          (transfer.getTransferData(DataFlavor.stringFlavor));
        String quotingString = "> ";
        StringBuffer quotedString = new StringBuffer("");
        boolean appendQuotaion = true;

        //Assemble the quoted string.
        StringTokenizer st = new StringTokenizer(clipboardData, "\n", true);
        while (st.hasMoreTokens()) {
          String token = st.nextToken();

          if (appendQuotaion)
            quotedString.append (quotingString);

          appendQuotaion = true;
          quotedString.append (token);

          //if this token is a line then skip inserting a quotation on the line
          if (!token.equals ("\n"))
            appendQuotaion = false;
        }

        //try to insert the quoted string.
        Document doc = mEditor.getDocument();
        try {
          doc.insertString(0 , quotedString.toString(), null);
          repaint();
        } catch (BadLocationException bl) {
        }
      }
      catch (UnsupportedFlavorException ufe){
      }
      catch(IOException ioe){
      }
    }
  }

  //-----------------------
  //"View" actions
  //-----------------------

  /**
   * Create a Toolbar
   * @see addToolbarButton
   */
  private GrendelToolBar createToolbar() {

    GrendelToolBar toolBar = new GrendelToolBar();

    toolBar.addButton( new SendNow(),
                     "send",       "Send",     "Send this message");
    toolBar.addButton( new SelectAddresses(),
                     "address",    "Address",  "Address this message");
    toolBar.addButton( new AttachFile(),
                     "attach",     "Attach",   "Include an attachment");
    toolBar.addButton( null,
                     "spelling",   "Spelling", "Check Spelling");
    toolBar.addButton( new SaveDraft(),
                     "save",       "Save",     "Save this message as a draft");
    toolBar.addButton( null,
                     "print",      "Print",    "Print this message");
    toolBar.addButton( null,
                     "stop",       "Stop",     "Stop the current Transfer (ESC)" );

    return toolBar;
  }


  /**
   * MessagePanel holds the subject and message body fields.
   * The MessagePanel is...
   *     1) The subject label "Subject:" (JLabel)
   *     2) The subject text field (JTextField)
   *     3) The editor (JTextArea)
   *
   * @see Composition
   */
  class MessagePanel extends JPanel {
    JTextArea   mEditor;
    JTextField  mSubjectText;

    public MessagePanel() {
      super(true);
      setLayout (new BorderLayout());

      //Subject (label and text field)
      JPanel subjectPanel = new JPanel (new BorderLayout(5, 0));
      subjectPanel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));

      JLabel subjectLabel = new JLabel("Subject:");
      subjectPanel.add (subjectLabel, BorderLayout.WEST);

      mSubjectText = new JTextField("");
      subjectPanel.add (mSubjectText, BorderLayout.CENTER);

      //add the subject panel to this panel.
      add(subjectPanel, BorderLayout.NORTH);

      //message body (text area)
      mEditor = createEditor();
      mEditor.setBorder(new BevelBorder(BevelBorder.LOWERED));
      add(new JScrollPane(mEditor), BorderLayout.CENTER);

      mEditor.setFont(Font.decode("Monospaced-12"));
    }

    /**
     * Creates a text editor for the message panel.
     * @return an editor.
     * @see getEditor
     */
    private JTextArea createEditor () {
      return new JTextArea(0, 70);
    }

    /**
     * return the messsagePanel's subject text field.
     * @return the text field used for the subject.
     */
    public JTextField getSubject () { return mSubjectText; }

    /**
     * return the messsagePanel's editor.
     * @return the editor used for the message panel.
     * @see createEditor
     */
    public JTextArea getEditor () { return mEditor; }
  }

  class PatchedMimeMessage extends MimeMessage {
    PatchedMimeMessage(Session s) {
      super(s);
    }
    public void writeTo(OutputStream o) throws MessagingException
    {
      try {
        if (getContentType().equals("text/plain")) {
          try {
            o.write(new String("Subject: ").getBytes());
            o.write(mSubject.getText().getBytes());
            o.write(new String("\n").getBytes());
            String text = (String) getContent();
            if (text == null) {
              text = mEditor.getText();
            }
            if (text != null) {
              o.write(text.getBytes());
            } else {
              System.err.println("Couldn't writeTo(): null content");
            }
          } catch (IOException e) {
            e.printStackTrace();
            throw new MessagingException("Couldn't writeTo(): " +
                                         e);
          }
        } else {
          super.writeTo(o);
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}
