pjs/grendel/composition/CompositionPanel.java

868 строки
26 KiB
Java

/* -*- 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.
*
* Created: Will Scullin <scullin@netscape.com>, 20 Oct 1997.
*/
package grendel.composition;
import calypso.util.ByteBuf;
import calypso.util.ByteLineBuffer;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.Frame;
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.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Properties;
import com.sun.java.swing.BorderFactory;
import com.sun.java.swing.ImageIcon;
import com.sun.java.swing.JButton;
import com.sun.java.swing.JLabel;
import com.sun.java.swing.JPanel;
import com.sun.java.swing.JScrollPane;
import com.sun.java.swing.JTextField;
import com.sun.java.swing.JTextArea;
import com.sun.java.swing.border.BevelBorder;
import com.sun.java.swing.event.ChangeEvent;
import com.sun.java.swing.event.EventListenerList;
import com.sun.java.swing.text.BadLocationException;
import com.sun.java.swing.text.Document;
import com.sun.java.swing.text.JTextComponent;
import com.sun.java.swing.text.TextAction;
import javax.mail.Address;
import javax.mail.Session;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.InternetAddress;
import netscape.orion.toolbars.NSButton;
import netscape.orion.toolbars.NSToolbar;
import netscape.orion.uimanager.AbstractUICmd;
import netscape.orion.uimanager.IUICmd;
import grendel.storage.MessageExtra;
import grendel.storage.MessageExtraFactory;
import grendel.ui.ActionFactory;
import grendel.ui.GeneralPanel;
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();
mAddressList = mAddressBar.getAddressList();
//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 IUICmd[] 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);
}
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 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 IUICmd[] 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 ViewAddress(),
new ViewAttachments(),
new ViewOptions(),
// new WrapLongLines()
};
//-----------------------
//"File" actions
//-----------------------
/**
* Try to save the message to the "draft" mailbox.
* @see SaveAs
*/
class SaveDraft extends AbstractUICmd {
SaveDraft() {
super(saveDraftTag);
setEnabled(true);
}
public void actionPerformed(ActionEvent e) {}
}
/**
* Try to save the message to a text file.
* @see SaveDraft
*/
class SaveAs extends AbstractUICmd {
SaveAs() {
super(saveAsTag);
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 AbstractUICmd {
SendNow() {
super(sendNowTag);
setEnabled(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) {
//get the users name who's sending this message.
Properties props = mSession.getProperties();
String userName =
props.getProperty("user.email_address");
if (userName != null) {
//create a mime message
Message msg = new PatchedMimeMessage(mSession);
try {
//set who's sending this message.
msg.setFrom (new InternetAddress(userName));
//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());
int deliverMode = Message.TO;
//map grendel.composition.Addressee delivery modes
// into javax.mail.Message delivery modes.
switch (recipients[i].getDelivery()) {
case Addressee.TO:
deliverMode = Message.TO;
break;
case Addressee.CC:
deliverMode = Message.CC;
break;
case Addressee.BCC:
deliverMode = Message.BCC;
break;
}
msg.addRecipients(deliverMode, toAddress);
}
msg.setSubject(mSubject.getText()); //set subject from text
//field.
msg.setSentDate(new java.util.Date()); //set date to now.
msg.setContent(messageText, "text/plain"); //contents.
msg.send(); //send the message.
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();
}
} else {
System.err.println("user.email_address undefined");
}
}
if (success) {
//hide this frame after sending mail.
notifyMailSent();
} else {
notifySendFailed();
}
}
}
/**
* Quote the original text message into the editor.
* @see PasteAsQuotation
*/
class QuoteOriginalText extends AbstractUICmd {
QuoteOriginalText() {
super(quoteOriginalTextTag);
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 (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 = "\n" + author + " wrote:"; // 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, "\n> ", null);
position += 3;
doc.insertString(position, line.toString(), null);
position += line.length();
} catch (BadLocationException e) {
}
}
}
repaint();
try {
plaintext.close();
} catch (IOException e) {
}
}
}
class SelectAddresses extends AbstractUICmd {
SelectAddresses() {
super(selectAddressesTag);
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 AbstractUICmd {
AttachFile() {
super(fileTag);
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 AbstractUICmd {
PasteAsQuotation() {
super(pasteAsQuotationTag);
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 quotitona 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
//-----------------------
class ViewAddress extends AbstractUICmd {
ViewAddress() {
super(viewAddressTag);
setEnabled(true);
}
public void actionPerformed(ActionEvent e) {
mAddressBar.setSelectedIndex(0);
}
}
class ViewAttachments extends AbstractUICmd {
ViewAttachments() {
super(viewAttachmentsTag);
setEnabled(true);
}
public void actionPerformed(ActionEvent e) {
mAddressBar.setSelectedIndex(1);
}
}
class ViewOptions extends AbstractUICmd {
ViewOptions() {
super(viewOptionsTag);
setEnabled(true);
}
public void actionPerformed(ActionEvent e) {
mAddressBar.setSelectedIndex(2);
}
}
/**
* Create a Toolbar
* @see addToolbarButton
*/
private NSToolbar createToolbar() {
NSToolbar toolBar = new NSToolbar();
addToolbarButton(toolBar, new SendNow(),
"send", "Send this message");
addToolbarButton(toolBar, new QuoteOriginalText(),
"quote", "Quote the previous document");
addToolbarButton(toolBar, new SelectAddresses(),
"address", "Address this message");
addToolbarButton(toolBar, new AttachFile(),
"attach", "Include an attachment");
addToolbarButton(toolBar, null,
"spelling", "Check Spelling");
addToolbarButton(toolBar, new SaveDraft(),
"save", "Save this message as a draft");
addToolbarButton(toolBar, null,
"security", "Show security Information");
addToolbarButton(toolBar, null,
"stop", "Stop the current Transfer (ESC)" );
return toolBar;
}
/**
* create a toolbar button
* @param aToolBar The parent toolbar to add this button to.
* @param aActionListener Who you want to be notified when the button is
* pressed.
* @param aImageName The image name for the button. like "save.gif"
* @param aToolTip The buttons tool tip. like "Save the current file".
* @see createToolbar
*/
public void addToolbarButton(NSToolbar aToolBar,
IUICmd aActionListener,
String aImageName,
String aToolTip) {
NSButton b = new NSButton();
b.setHorizontalTextPosition(JButton.CENTER);
b.setVerticalTextPosition(JButton.BOTTOM);
b.setToolTipText(aToolTip);
URL iconUrl = getClass().getResource("images/" + aImageName + ".gif");
b.setIcon(new ImageIcon(iconUrl));
iconUrl = getClass().getResource("images/" +
aImageName + "-disabled.gif");
b.setDisabledIcon(new ImageIcon(iconUrl));
iconUrl = getClass().getResource("images/" +
aImageName + "-pressed.gif");
b.setPressedIcon(new ImageIcon(iconUrl));
iconUrl = getClass().getResource("images/" +
aImageName + "-rollover.gif");
b.setRolloverIcon(new ImageIcon(iconUrl));
// JButton b = new JButton(new ImageIcon(aImageName));
// b.setToolTipText(aToolTip);
// b.setPad(new Insets(3,3,3,3));
if (aActionListener != null) {
b.addActionListener(aActionListener);
} else {
b.setEnabled(false);
}
aToolBar.addItem(b);
}
/**
* 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();
}
}
}
}