Difference between revisions of "Teams Winter 2011/team1/BlackBerry/Add Send Email Option"
Line 1: | Line 1: | ||
− | == Adding Send Email Functionality == | + | == 9. Adding Send Email Functionality == |
− | 9.1 | + | 9.1. Add the Send email option to the menu and implement it: |
− | <source lang="java"> | + | <source lang="java"> |
− | + | // Send email to student | |
− | + | ImageMenuItem emailItem = new ImageMenuItem("Email Student", 400, 4, MENU_EMAIL); | |
+ | emailItem.setCommand(new Command(new CommandHandler() | ||
+ | { | ||
+ | public void execute(ReadOnlyCommandMetadata metadata, Object context) | ||
+ | { | ||
+ | Student student = (Student) _keywordFilterField.getSelectedElement(); | ||
− | + | if (student != null) | |
+ | { | ||
+ | try | ||
+ | { | ||
+ | ComposeScreen composeScreen = new ComposeScreen(null, _store, student.getEmail()); | ||
+ | pushScreen(composeScreen); | ||
+ | } | ||
+ | catch(Exception ex) | ||
+ | { | ||
+ | Dialog.alert("composing screen: " + ex.getMessage()); | ||
+ | } | ||
− | <source | + | } |
− | // | + | } |
− | + | })); | |
− | + | _mainScreen.addMenuItem(emailItem); | |
+ | </source> | ||
+ | 9.2 Add four new Screen classes and a helper class to the project: | ||
+ | * <code>ComposeScreen.java</code> - start screen where email is composed | ||
+ | * <code>MessageScreen.java</code> - screen showing the email | ||
+ | * <code>MessagesViewScreen.java</code> - to view the created email | ||
+ | * <code>MessageListField.java</code> - handles the drawing of the list of messages | ||
+ | * <code>FoldersViewScreen.java</code> - view emails in the Outbox folder | ||
+ | The main functionality is taken from the Sample provided with blackberry plug-in: <code>BlackBerryMailDemo</code><br/> | ||
+ | 9.3. Following is the modified code to accommodate the application:<br/> | ||
+ | <code>ComposeScreen.java</code> | ||
+ | <source lang="java"> | ||
+ | package cs.ecl.team1.project; | ||
− | + | import java.util.*; | |
− | + | import net.rim.blackberry.api.mail.*; | |
+ | import net.rim.device.api.command.*; | ||
+ | import net.rim.device.api.ui.component.*; | ||
+ | import net.rim.device.api.ui.*; | ||
+ | import net.rim.device.api.util.*; | ||
− | |||
− | |||
− | |||
− | + | /** | |
+ | * The ComposeScreen is a screen which displays either a new or saved message. | ||
+ | * It adds the functionality of saving and sending messages to its parent class, | ||
+ | * MessageScreen. | ||
+ | */ | ||
+ | public final class ComposeScreen extends MessageScreen | ||
+ | { | ||
+ | private static final int FIRST = 0; | ||
+ | private static final int SEND_MENU_ITEM_INDEX = 0; | ||
+ | |||
+ | private Store _store; | ||
+ | private MessagesViewScreen _messagesViewScreen; | ||
− | + | ||
− | + | /** | |
− | + | * Creates a new ComposeScreen object | |
− | + | * @param message A message in the process of being composed, or null if a new message is to be composed | |
− | + | * @param store The message store for this application | |
− | + | */ | |
+ | |||
+ | public ComposeScreen(Message message, Store store, String email) | ||
+ | { | ||
+ | super(message, true, email); | ||
+ | _store = store; | ||
+ | _messagesViewScreen = new MessagesViewScreen(); | ||
+ | |||
+ | // If a new message is to be created, indicate this in the title | ||
+ | if( message == null ) | ||
+ | { | ||
+ | setTitle("New Message"); | ||
+ | } | ||
− | + | // Create and add menu items specific to the Compose action (addTo, | |
− | + | // addBcc, addCc, etc...). | |
− | + | AddHeaderFieldAction addToMenuItem = new AddHeaderFieldAction(Message.RecipientType.TO, "Add To: ", "To: "); | |
− | + | AddHeaderFieldAction addCcMenuItem = new AddHeaderFieldAction(Message.RecipientType.CC, "Add Cc: ", "Cc: "); | |
− | + | AddHeaderFieldAction addBccMenuItem = new AddHeaderFieldAction(Message.RecipientType.BCC, "Add Bcc: ", "Bcc: "); | |
− | + | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | // MenuItem to save a message | |
− | <source lang="java"> | + | MenuItem saveMenuItem = new MenuItem(new StringProvider("Save Message"), 0x230020, 1); |
− | catch ( | + | saveMenuItem.setCommand(new Command(new CommandHandler() |
− | + | { | |
− | + | /** | |
− | + | * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object) | |
− | + | */ | |
− | </source> | + | public void execute(ReadOnlyCommandMetadata metadata, Object context) |
+ | { // If the save is completed, then discard this screen | ||
+ | if( onSave() ) | ||
+ | { | ||
+ | close(); | ||
+ | } | ||
+ | else | ||
+ | // If the message could not be saved, alert the user | ||
+ | { | ||
+ | UiApplication.getUiApplication().invokeLater(new Runnable() | ||
+ | { | ||
+ | public void run() | ||
+ | { | ||
+ | Dialog.alert("Message could not be saved"); | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | } | ||
+ | })); | ||
+ | |||
+ | // MenuItem to send a message | ||
+ | MenuItem sendMenuItem = new MenuItem(new StringProvider("Send Message"), 0x230010, 0); | ||
+ | sendMenuItem.setCommand(new Command(new CommandHandler() | ||
+ | { | ||
+ | /** | ||
+ | * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object) | ||
+ | */ | ||
+ | public void execute(ReadOnlyCommandMetadata metadata, Object context) | ||
+ | { // If the save is completed, then discard this screen | ||
+ | try | ||
+ | { | ||
+ | _message = getMessage(); | ||
+ | if( _message != null ) | ||
+ | { | ||
+ | // Send the message | ||
+ | Transport.send(_message); | ||
+ | |||
+ | UiApplication.getUiApplication().pushScreen(_messagesViewScreen); | ||
+ | close(); | ||
+ | } | ||
+ | } | ||
+ | catch( MessagingException e ) | ||
+ | { | ||
+ | ViewStudentApp.errorDialog("Transport.send(Message) threw " + e.toString()); | ||
+ | } | ||
+ | } | ||
+ | })); | ||
+ | |||
+ | addMenuItem(sendMenuItem); | ||
+ | addMenuItem(saveMenuItem); | ||
+ | addMenuItem(addToMenuItem); | ||
+ | addMenuItem(addCcMenuItem); | ||
+ | addMenuItem(addBccMenuItem); | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Overrides MessageScreen.displayMessage(). The message's 'sent' properties | ||
+ | * are not displayed since the message is still in the process of editing. | ||
+ | */ | ||
+ | void displayMessage(String email) | ||
+ | { | ||
+ | // If the message does not exist then compose a new message | ||
+ | if( _message == null ) | ||
+ | { | ||
+ | // Add a To line | ||
+ | EditField toField = new EditField("To: ", email, 40, BasicEditField.FILTER_EMAIL); | ||
+ | addTextFieldToTableAndScreen(toField, Message.RecipientType.TO); | ||
+ | |||
+ | // Add a subject line | ||
+ | EditField subjectField = new EditField("Subject: ", ""); | ||
+ | addTextFieldToTableAndScreen(subjectField, SUBJECT); | ||
+ | |||
+ | // Add a separator between the body and the headers | ||
+ | add(new SeparatorField()); | ||
+ | |||
+ | // Add a body field | ||
+ | EditField bodyField = new EditField(); | ||
+ | addTextFieldToTableAndScreen(bodyField, BODY); | ||
+ | } | ||
+ | else | ||
+ | // The message exists so display it | ||
+ | { | ||
+ | displayHeader(); | ||
+ | add(new SeparatorField()); | ||
+ | displayMessageBody(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Gets a message for sending or saving | ||
+ | * @return A new message | ||
+ | */ | ||
+ | Message getMessage() | ||
+ | { | ||
+ | // Find an outbox folder and use it to construct a new message | ||
+ | Folder outbox = _store.findFolder("Outbox")[ FIRST ]; | ||
+ | Message message = new Message(outbox); | ||
+ | |||
+ | // Add all the current headers | ||
+ | for( int keyNo = 0; keyNo < HEADER_KEYS.length; keyNo++ ) | ||
+ | { | ||
+ | Vector fieldsByType = (Vector) _fieldTable.get(HEADER_KEYS[ keyNo ]); | ||
+ | |||
+ | if( fieldsByType != null ) | ||
+ | { | ||
+ | // Build a vector of all the addresses | ||
+ | Vector addressVector = new Vector(); | ||
+ | int size = fieldsByType.size(); | ||
+ | for( int fieldNo = 0; fieldNo < size; fieldNo++ ) | ||
+ | { | ||
+ | TextField addressField = (TextField) fieldsByType.elementAt(fieldNo); | ||
+ | |||
+ | // Try to create a new address object wrapping the email | ||
+ | // address and add it to the address vector. | ||
+ | try | ||
+ | { | ||
+ | addressVector.addElement(new Address(addressField.getText(), "")); | ||
+ | } | ||
+ | catch( AddressException e ) // Invalid address | ||
+ | { | ||
+ | ViewStudentApp.errorDialog("Address(String, String) threw " + e.toString()); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Dump the vector of addresses into an array to send the message | ||
+ | Address[] addresses = new Address[ addressVector.size() ]; | ||
+ | addressVector.copyInto(addresses); | ||
+ | |||
+ | // Try to add the addresses to the message's list of recipients | ||
+ | try | ||
+ | { | ||
+ | message.addRecipients(HEADER_KEYS[ keyNo ], addresses); | ||
+ | } | ||
+ | catch( MessagingException e ) | ||
+ | { | ||
+ | ViewStudentApp.errorDialog("Message#addRecipients(int, Address[]) threw " + e.toString()); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Add the subject | ||
+ | Vector subjectFields = (Vector) _fieldTable.get(SUBJECT); | ||
+ | TextField subjectField = (TextField) subjectFields.elementAt(FIRST); | ||
+ | |||
+ | if( subjectFields != null && subjectFields.size() > 0 ) | ||
+ | { | ||
+ | message.setSubject(subjectField.getText()); | ||
+ | } | ||
+ | |||
+ | // Add the body by adding all the body fields into one multipart | ||
+ | Vector bodyFields = (Vector) _fieldTable.get(BODY); | ||
+ | if( bodyFields != null ) | ||
+ | { | ||
+ | int size = bodyFields.size(); | ||
+ | Multipart content = new Multipart(); | ||
+ | for( int fieldNo = 0; fieldNo < size; fieldNo++ ) | ||
+ | { | ||
+ | TextField body = (TextField) bodyFields.elementAt(fieldNo); | ||
+ | content.addBodyPart(new TextBodyPart(content, body.getText())); | ||
+ | } | ||
+ | try | ||
+ | { | ||
+ | message.setContent(content); | ||
+ | } | ||
+ | catch( MessagingException e ) | ||
+ | { | ||
+ | ViewStudentApp.errorDialog("Message#setContent(Object) threw " + e.toString()); | ||
+ | } | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | ViewStudentApp.errorDialog("Error: no body field available"); | ||
+ | return null; | ||
+ | } | ||
+ | |||
+ | // Set the date | ||
+ | message.setSentDate(Calendar.getInstance().getTime()); | ||
+ | |||
+ | return message; | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * @see net.rim.device.api.ui.Screen#onSave() | ||
+ | */ | ||
+ | protected boolean onSave() | ||
+ | { | ||
+ | // Save the message to the outbox | ||
+ | try | ||
+ | { | ||
+ | Message newMessage = getMessage(); | ||
+ | if( newMessage != null ) | ||
+ | { | ||
+ | // Retrieve an outbox to save the message in | ||
+ | Store store = Session.waitForDefaultSession().getStore(); | ||
+ | Folder[] allOutboxFolders = store.list(Folder.OUTBOX); | ||
+ | |||
+ | Folder outbox = null; | ||
+ | for( int i = allOutboxFolders.length - 1; i >= 0 ; --i ) | ||
+ | { | ||
+ | if( allOutboxFolders[ i ].getParent().getName().startsWith("Mailbox") ) | ||
+ | { | ||
+ | outbox = allOutboxFolders[ i ]; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Save the new message and replace the old one if it exists | ||
+ | outbox.appendMessage(newMessage); | ||
+ | if( _message != null ) | ||
+ | { | ||
+ | outbox.deleteMessage(_message, true); | ||
+ | } | ||
+ | _message = newMessage; | ||
+ | |||
+ | // Set the status to composing and flag that it has been saved | ||
+ | _message.setStatus(Message.Status.TX_COMPOSING, Message.Status.TX_ERROR); | ||
+ | _message.setFlag(Message.Flag.SAVED, true); | ||
+ | |||
+ | return true; | ||
+ | } | ||
+ | |||
+ | return false; | ||
+ | } | ||
+ | catch( MessagingException e ) | ||
+ | { | ||
+ | return false; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Make "Send" the default menu item. | ||
+ | * | ||
+ | * @see net.rim.device.api.ui.container.MainScreen#makeMenu(Menu,int) | ||
+ | */ | ||
+ | protected void makeMenu(Menu menu, int instance) | ||
+ | { | ||
+ | super.makeMenu(menu, instance); | ||
+ | |||
+ | // menu.setDefault(SEND_MENU_ITEM_INDEX); | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * This class is responsible for adding the various header fields to the | ||
+ | * compose screen (To, Bcc, CC). | ||
+ | */ | ||
+ | private final class AddHeaderFieldAction extends MenuItem | ||
+ | { | ||
+ | private String _fieldLabelText; | ||
+ | private int _headerType; | ||
+ | |||
+ | /** | ||
+ | * Constructs a menu item which adds a header of a specified type to the | ||
+ | * compose screen. | ||
+ | * | ||
+ | * @param headerType One of the Message.RecipientType fields | ||
+ | * @param menuItemText String to use for the menu item | ||
+ | * @param fieldLabelText String to use for the label of this field | ||
+ | */ | ||
+ | AddHeaderFieldAction(int headerType, String menuItemText, String fieldLabelText) | ||
+ | { | ||
+ | super(new StringProvider(menuItemText), 0x240010, 2); | ||
+ | |||
+ | _fieldLabelText = fieldLabelText; | ||
+ | _headerType = headerType; | ||
+ | this.setCommand(new Command(new CommandHandler() | ||
+ | { | ||
+ | /** | ||
+ | * Adds a new header field to the message. The field is placed so that | ||
+ | * the header types are grouped together and the most recently added one | ||
+ | * is closest to the bottom. | ||
+ | * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object) | ||
+ | */ | ||
+ | public void execute(ReadOnlyCommandMetadata metadata, Object context) | ||
+ | { | ||
+ | EditField newField = new EditField(_fieldLabelText, ""); | ||
+ | |||
+ | // Find out where the last field of this type was added to the | ||
+ | // screen. | ||
+ | Vector fieldsByType = (Vector) _fieldTable.get(_headerType); | ||
+ | int lastInsertedIndex; | ||
+ | if( fieldsByType == null ) | ||
+ | { | ||
+ | // If a field of _headerType was not made yet, then create the | ||
+ | // vector which contains all of the fields of _headerType. | ||
+ | fieldsByType = new Vector(); | ||
+ | _fieldTable.put(_headerType, fieldsByType); | ||
+ | lastInsertedIndex = getIndexForNewFieldType(); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | lastInsertedIndex = getIndexOfLastFieldOfType(_headerType); | ||
+ | } | ||
+ | |||
+ | // Add the new field to both the screen and the vector keeping track | ||
+ | // of all the fields of the same type. | ||
+ | ComposeScreen.this.insert(newField, lastInsertedIndex + 1); | ||
+ | fieldsByType.addElement(newField); | ||
+ | newField.setFocus(); | ||
+ | } | ||
+ | })); | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Given the existing field type of this instance of the class, | ||
+ | * determine where to place new header fields in the screen. | ||
+ | * | ||
+ | * @return The index at which to insert the new header field | ||
+ | */ | ||
+ | private int getIndexForNewFieldType() | ||
+ | { | ||
+ | // Note: we don't handle TO here since there ALWAYS must be one TO | ||
+ | // field. | ||
+ | switch( _headerType ) | ||
+ | { | ||
+ | // Find the last TO field and use the next index as the | ||
+ | // insertion point. | ||
+ | case Message.RecipientType.CC: | ||
+ | return getIndexOfLastFieldOfType(Message.RecipientType.TO); | ||
+ | |||
+ | // Try to find the last CC field and use it as the next | ||
+ | // insertion point. If no CC field exists then find the last | ||
+ | // TO field and use its index as the insertion point. | ||
+ | case Message.RecipientType.BCC: | ||
+ | int index = getIndexOfLastFieldOfType(Message.RecipientType.CC); | ||
+ | if( index == -1 ) | ||
+ | return getIndexOfLastFieldOfType(Message.RecipientType.TO); | ||
+ | return index; | ||
+ | |||
+ | default: | ||
+ | throw new IllegalStateException("Mail Demo: Unrecognized recipient type"); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Retrieves the index of the last field added of a specified type. | ||
+ | * | ||
+ | * @param type The type of header field to retrieve the last index of | ||
+ | * @return The index of the most recently added field of the specified | ||
+ | * type, -1 if a field of the specified type has not been added | ||
+ | * yet. | ||
+ | */ | ||
+ | private int getIndexOfLastFieldOfType(int type) | ||
+ | { | ||
+ | Vector fields = (Vector) _fieldTable.get(type); | ||
+ | if( fields == null ) | ||
+ | { | ||
+ | return -1; | ||
+ | } | ||
+ | |||
+ | Field field = (Field) fields.lastElement(); | ||
+ | |||
+ | return field.getIndex(); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | </source> | ||
+ | <code>MessageScreen.java</code> | ||
+ | <source lang="java"> | ||
+ | package cs.ecl.team1.project; | ||
+ | |||
+ | import net.rim.device.api.ui.container.*; | ||
+ | import net.rim.device.api.ui.component.*; | ||
+ | import net.rim.device.api.util.IntHashtable; | ||
+ | import javax.microedition.pim.Contact; | ||
+ | import java.util.*; | ||
+ | |||
+ | import net.rim.blackberry.api.mail.*; | ||
+ | |||
+ | /** | ||
+ | * The MessageScreen class allows a user to view a selected message and | ||
+ | * edit the message if the screen is marked editable. It manages the different | ||
+ | * TextFields using a hashtable where the type of information that is held | ||
+ | * in a given TextField is the key while the value is a Vector of TextFields | ||
+ | * associated with that information type. This class supports displaying | ||
+ | * plain text, Mime, supported and unsupported attachments and pdap contacts. | ||
+ | */ | ||
+ | public class MessageScreen extends MainScreen | ||
+ | { | ||
+ | // Constants | ||
+ | public final static String NO_SUBJECT = "<No Subject>"; | ||
+ | public final static String UNKNOWN_NAME = "<?>"; | ||
+ | |||
+ | protected final static int SUBJECT = 0; | ||
+ | protected final static int BODY = 1; | ||
+ | protected final static int INFO = 2; | ||
+ | protected final static int[] HEADER_KEYS = { Message.RecipientType.TO, Message.RecipientType.CC, Message.RecipientType.BCC }; | ||
+ | protected final static String[] HEADER_NAMES = { "To: ", "Cc: ", "Bcc: " }; | ||
+ | |||
+ | private final static int MAX_CHARS = 128; | ||
+ | |||
+ | protected IntHashtable _fieldTable; | ||
+ | protected Message _message; | ||
+ | private boolean _editable; | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Creates a new MessageScreen object | ||
+ | * @param message The message to display | ||
+ | * @param editable True is the message is editable, otherwise false | ||
+ | */ | ||
+ | public MessageScreen(Message message, boolean editable, String email) | ||
+ | { | ||
+ | _fieldTable = new IntHashtable(); | ||
+ | _editable = editable; | ||
+ | |||
+ | // Set the message and display its subject as the title if the | ||
+ | // message exists. | ||
+ | _message = message; | ||
+ | if( _message != null ) | ||
+ | setTitle(_message.getSubject()); | ||
+ | |||
+ | displayMessage(email); | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Displays the message | ||
+ | */ | ||
+ | void displayMessage(String email) | ||
+ | { | ||
+ | displayMessageInformation(); | ||
+ | |||
+ | add(new SeparatorField()); | ||
+ | |||
+ | displayHeader(); | ||
+ | |||
+ | add(new SeparatorField()); | ||
+ | |||
+ | displayMessageBody(); | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Displays information about the message's send and recieve properties | ||
+ | */ | ||
+ | protected void displayMessageInformation() | ||
+ | { | ||
+ | // Add a field describing the source service | ||
+ | ServiceConfiguration sc = _message.getFolder().getStore().getServiceConfiguration(); | ||
+ | EditField service = new EditField("Service: ", sc.getName(), MAX_CHARS, EditField.READONLY | EditField.NON_FOCUSABLE); | ||
+ | addTextFieldToTableAndScreen(service, INFO); | ||
+ | |||
+ | // Add the folder field | ||
+ | EditField folder = new EditField("Folder: ", _message.getFolder().getName(), MAX_CHARS, EditField.READONLY | ||
+ | | EditField.NON_FOCUSABLE); | ||
+ | addTextFieldToTableAndScreen(folder, INFO); | ||
+ | |||
+ | // Add the status of the message | ||
+ | String statusString = getStatusString(_message); | ||
+ | EditField status = new EditField("Status: ", statusString, MAX_CHARS, EditField.READONLY | EditField.NON_FOCUSABLE); | ||
+ | addTextFieldToTableAndScreen(status, INFO); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Displays information about the destination and source of the message as | ||
+ | * well as its subject. | ||
+ | */ | ||
+ | protected void displayHeader() | ||
+ | { | ||
+ | // Assign the appropriate EditField style property | ||
+ | long editableStyle = _editable ? EditField.EDITABLE : EditField.READONLY; | ||
+ | |||
+ | // Display the headers (To:, Cc:, Bcc:) | ||
+ | for( int key = 0; key < HEADER_KEYS.length; key++ ) | ||
+ | { | ||
+ | try | ||
+ | { | ||
+ | Address[] addresses = _message.getRecipients(HEADER_KEYS[ key ]); | ||
+ | for( int index = 0; index < addresses.length; index++ ) | ||
+ | { | ||
+ | // Retrieve the name | ||
+ | String name = addresses[ index ].getName(); | ||
+ | if( name == null || name.length() == 0 ) | ||
+ | { | ||
+ | name = addresses[ index ].getAddr(); | ||
+ | } | ||
+ | |||
+ | // Create the edit field, associate the address to the field | ||
+ | // and add it to the screen and collection of fields. | ||
+ | EditField headerField = new EditField(HEADER_NAMES[ key ], name, EditField.DEFAULT_MAXCHARS, editableStyle); | ||
+ | headerField.setCookie(addresses[ index ]); | ||
+ | |||
+ | addTextFieldToTableAndScreen(headerField, HEADER_KEYS[ key ]); | ||
+ | } | ||
+ | } | ||
+ | catch( MessagingException e ) | ||
+ | { | ||
+ | ViewStudentApp.errorDialog("Error: could not retrieve message header."); | ||
+ | close(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Display the 'Sent' date if it is available | ||
+ | Date sent = _message.getSentDate(); | ||
+ | if( sent != null ) | ||
+ | { | ||
+ | EditField sentDate = new EditField("Sent: ", Util.getDateAsString(sent), EditField.DEFAULT_MAXCHARS, EditField.READONLY | ||
+ | | EditField.NON_FOCUSABLE); | ||
+ | |||
+ | // Change the label to "Saved: " if the message hasn't been sent yet | ||
+ | if( _message.getStatus() == Message.Status.TX_COMPOSING ) | ||
+ | sentDate.setLabel("Saved: "); | ||
+ | |||
+ | add(sentDate); | ||
+ | } | ||
+ | |||
+ | // Display the subject field | ||
+ | String subject = _message.getSubject(); | ||
+ | if( subject == null ) | ||
+ | subject = NO_SUBJECT; | ||
+ | |||
+ | EditField subjectField = new EditField("Subject: ", subject, EditField.DEFAULT_MAXCHARS, editableStyle); | ||
+ | addTextFieldToTableAndScreen(subjectField, SUBJECT); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Displays the message body | ||
+ | */ | ||
+ | protected void displayMessageBody() | ||
+ | { | ||
+ | // Retrieve the parent of the message body | ||
+ | Object obj = _message.getContent(); | ||
+ | Multipart parent = null; | ||
+ | if( obj instanceof MimeBodyPart || obj instanceof TextBodyPart ) | ||
+ | { | ||
+ | BodyPart bp = (BodyPart) obj; | ||
+ | parent = bp.getParent(); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | parent = (Multipart) obj; | ||
+ | } | ||
+ | |||
+ | // Display the message body | ||
+ | String mpType = parent.getContentType(); | ||
+ | if( mpType.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING) | ||
+ | || mpType.equals(BodyPart.ContentType.TYPE_MULTIPART_MIXED_STRING) ) | ||
+ | { | ||
+ | displayMultipart(parent); | ||
+ | } | ||
+ | |||
+ | // Ensure there is at least one body field if nothing was displayed | ||
+ | Vector bodyVector = (Vector) _fieldTable.get(BODY); | ||
+ | if( bodyVector == null || bodyVector.size() == 0 ) | ||
+ | { | ||
+ | if( _editable ) | ||
+ | { | ||
+ | addTextFieldToTableAndScreen(new EditField("", ""), BODY); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | addTextFieldToTableAndScreen(new RichTextField(""), BODY); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Processes a multi-part message by displaying its body parts. Text body | ||
+ | * parts are displayed before attachments and if a multi body part is | ||
+ | * encountered, then it is processed through recursion by calling this method | ||
+ | * on it. | ||
+ | * | ||
+ | * @param multipart The multi-part to display | ||
+ | * @param editable True if this multi-part is editable | ||
+ | */ | ||
+ | protected void displayMultipart(Multipart multipart) | ||
+ | { | ||
+ | // This vector stores fields which are to be displayed only after all | ||
+ | // of the body fields are displayed. (Attachments and Contacts). | ||
+ | Vector delayedFields = new Vector(); | ||
+ | |||
+ | // Process each part of the multi-part, taking the appropriate action | ||
+ | // depending on the part's type. This loop should: display text and | ||
+ | // html body parts, recursively display multi-parts and store | ||
+ | // attachments and contacts to display later. | ||
+ | for( int index = 0; index < multipart.getCount(); index++ ) | ||
+ | { | ||
+ | BodyPart bodyPart = multipart.getBodyPart(index); | ||
+ | |||
+ | // If this body part is text then display all of it | ||
+ | if( bodyPart instanceof TextBodyPart ) | ||
+ | { | ||
+ | TextBodyPart textBodyPart = (TextBodyPart) bodyPart; | ||
+ | |||
+ | // If there are missing parts of the text, try to retrieve the | ||
+ | // rest of it. | ||
+ | if( textBodyPart.hasMore() ) | ||
+ | { | ||
+ | try | ||
+ | { | ||
+ | Transport.more(textBodyPart, true); | ||
+ | } | ||
+ | catch( Exception e ) | ||
+ | { | ||
+ | ViewStudentApp.errorDialog("Transport.more(BodyPart, boolean) threw " + e.toString()); | ||
+ | } | ||
+ | } | ||
+ | String plainText = (String) textBodyPart.getContent(); | ||
+ | |||
+ | // Display the plain text, using an EditField if the message is | ||
+ | // editable or a RichTextField if it is not editable. Note: this | ||
+ | // does not add any empty fields. | ||
+ | if( plainText.length() != 0 ) | ||
+ | { | ||
+ | if( _editable ) | ||
+ | { | ||
+ | addTextFieldToTableAndScreen(new EditField("", plainText), BODY); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | addTextFieldToTableAndScreen(new RichTextField(plainText), BODY); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | else if( bodyPart instanceof MimeBodyPart ) | ||
+ | { | ||
+ | MimeBodyPart mimeBodyPart = (MimeBodyPart) bodyPart; | ||
+ | |||
+ | // If the content is text then display it | ||
+ | String contentType = mimeBodyPart.getContentType(); | ||
+ | if( contentType.startsWith(BodyPart.ContentType.TYPE_TEXT_HTML_STRING) ) | ||
+ | { | ||
+ | Object obj = mimeBodyPart.getContent(); | ||
+ | if( obj != null ) | ||
+ | { | ||
+ | String htmlText = new String((byte[]) obj); | ||
+ | addTextFieldToTableAndScreen(new RichTextField(htmlText), BODY); | ||
+ | } | ||
+ | } | ||
+ | else if( contentType.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING) ) | ||
+ | { | ||
+ | // If the body part is a multi-part and it has the the | ||
+ | // content type of TYPE_MULTIPART_ALTERNATIVE_STRING, then | ||
+ | // recursively display the multi-part. | ||
+ | Object obj = mimeBodyPart.getContent(); | ||
+ | if( obj instanceof Multipart ) | ||
+ | { | ||
+ | Multipart childMultipart = (Multipart) obj; | ||
+ | String childMultipartType = childMultipart.getContentType(); | ||
+ | if( childMultipartType.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING) ) | ||
+ | { | ||
+ | displayMultipart(childMultipart); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | else if( bodyPart instanceof SupportedAttachmentPart || bodyPart instanceof UnsupportedAttachmentPart ) | ||
+ | { | ||
+ | // Extract the content type and name from the attachments | ||
+ | String contentType = bodyPart.getContentType(); | ||
+ | String name; | ||
+ | if( bodyPart instanceof UnsupportedAttachmentPart ) | ||
+ | { | ||
+ | UnsupportedAttachmentPart uap = (UnsupportedAttachmentPart) bodyPart; | ||
+ | name = uap.getName(); | ||
+ | } | ||
+ | else // The bodyPart is a SupportedAttachmentPart | ||
+ | { | ||
+ | SupportedAttachmentPart sap = (SupportedAttachmentPart) bodyPart; | ||
+ | name = sap.getName(); | ||
+ | } | ||
+ | |||
+ | // Format the content type and name to display and store | ||
+ | // the field. | ||
+ | StringBuffer sb = new StringBuffer(contentType.length() + name.length() + 2); | ||
+ | sb.append(contentType); | ||
+ | sb.append('['); | ||
+ | sb.append(name); | ||
+ | sb.append(']'); | ||
+ | |||
+ | delayedFields.addElement(new RichTextField(sb.toString())); | ||
+ | } | ||
+ | else if( bodyPart instanceof PDAPContactAttachmentPart ) | ||
+ | { | ||
+ | Contact contact = (Contact) bodyPart.getContent(); | ||
+ | |||
+ | // Build the contact name | ||
+ | StringBuffer sb = new StringBuffer("Contact: "); | ||
+ | if( contact.countValues(Contact.NAME) > 0 ) | ||
+ | { | ||
+ | String[] name = contact.getStringArray(Contact.NAME, 0); | ||
+ | |||
+ | if( name[ Contact.NAME_PREFIX ] != null ) | ||
+ | { | ||
+ | sb.append(name[ Contact.NAME_PREFIX ]); | ||
+ | sb.append(' '); | ||
+ | } | ||
+ | |||
+ | if( name[ Contact.NAME_GIVEN ] != null ) | ||
+ | { | ||
+ | sb.append(name[ Contact.NAME_GIVEN ]); | ||
+ | sb.append(' '); | ||
+ | } | ||
+ | |||
+ | if( name[ Contact.NAME_FAMILY ] != null ) | ||
+ | { | ||
+ | sb.append(name[ Contact.NAME_FAMILY ]); | ||
+ | } | ||
+ | |||
+ | // Trim the last space of the name if it exists | ||
+ | int lastChar = sb.length() - 1; | ||
+ | if( sb.charAt(lastChar) == ' ' ) | ||
+ | sb.deleteCharAt(lastChar); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | sb.append(UNKNOWN_NAME); | ||
+ | } | ||
+ | |||
+ | // Create the contact attachment field and store it | ||
+ | RichTextField contactAttachment = new RichTextField(sb.toString()); | ||
+ | contactAttachment.setCookie(contact); | ||
+ | delayedFields.addElement(contactAttachment); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Now that the body parts have been displayed, display the queued | ||
+ | // fields while separating them by inserting a separator field. | ||
+ | for( int index = 0; index < delayedFields.size(); index++ ) | ||
+ | { | ||
+ | add(new SeparatorField()); | ||
+ | addTextFieldToTableAndScreen((TextField) delayedFields.elementAt(index), BODY); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Compiles the status of a message into a readable string. | ||
+ | * | ||
+ | * @param message The message whose status is to be compiled into a string | ||
+ | * @return The string displaying the status of the message | ||
+ | */ | ||
+ | public static String getStatusString(Message message) | ||
+ | { | ||
+ | StringBuffer statusStrBuffer = new StringBuffer(); | ||
+ | |||
+ | // Add any errors to the status string if it applies | ||
+ | int status = message.getStatus(); | ||
+ | if( status == Message.Status.RX_ERROR ) | ||
+ | { | ||
+ | statusStrBuffer.append("RX ERROR, "); | ||
+ | } | ||
+ | |||
+ | if( status == Message.Status.TX_GENERAL_FAILURE ) | ||
+ | { | ||
+ | statusStrBuffer.append("RX ERROR, "); | ||
+ | } | ||
+ | |||
+ | if( status == Message.Status.TX_ERROR ) | ||
+ | { | ||
+ | statusStrBuffer.append("TX ERROR, "); | ||
+ | } | ||
+ | |||
+ | // Use the flags to add any message statuses | ||
+ | int flags = message.getFlags(); | ||
+ | if( 0 != (flags & Message.Flag.OPENED) ) | ||
+ | { | ||
+ | statusStrBuffer.append("Opened, "); | ||
+ | } | ||
+ | |||
+ | if( 0 != (flags & Message.Flag.SAVED) ) | ||
+ | { | ||
+ | statusStrBuffer.append("Saved, "); | ||
+ | } | ||
+ | |||
+ | if( 0 != (flags & Message.Flag.FILED) ) | ||
+ | { | ||
+ | statusStrBuffer.append("Filed, "); | ||
+ | } | ||
+ | |||
+ | // Check if the message has a high or low priority | ||
+ | byte messagePriority = message.getPriority(); | ||
+ | if( messagePriority == Message.Priority.HIGH ) | ||
+ | { | ||
+ | statusStrBuffer.append("High Priority, "); | ||
+ | } | ||
+ | else if( messagePriority == Message.Priority.LOW ) | ||
+ | { | ||
+ | statusStrBuffer.append("Low Priority, "); | ||
+ | } | ||
+ | |||
+ | // If there are any characters in the status string then delete the last | ||
+ | // two characters if there are any characters to delete. Should be | ||
+ | // either ", " or " ". | ||
+ | statusStrBuffer.delete(statusStrBuffer.length() - 2, statusStrBuffer.length()); | ||
+ | |||
+ | return statusStrBuffer.toString(); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Add a new field to the hashtable of TextFields and to the screen | ||
+ | * | ||
+ | * @param field The field to add | ||
+ | * @param type The type of field to add | ||
+ | */ | ||
+ | protected void addTextFieldToTableAndScreen(TextField field, int type) | ||
+ | { | ||
+ | Vector fieldsByType = (Vector) _fieldTable.get(type); | ||
+ | |||
+ | // If the vector of fields associated with the type is not made yet, | ||
+ | // initialize one and put it into the fields collection. | ||
+ | if( fieldsByType == null ) | ||
+ | { | ||
+ | fieldsByType = new Vector(1); | ||
+ | _fieldTable.put(type, fieldsByType); | ||
+ | } | ||
+ | |||
+ | fieldsByType.addElement(field); | ||
+ | add(field); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @see net.rim.device.api.ui.Screen#onClose() | ||
+ | */ | ||
+ | public boolean onClose() | ||
+ | { | ||
+ | // If the message status is "received", mark it "read" | ||
+ | if( _message != null && _message.getStatus() == Message.Status.RX_RECEIVED ) | ||
+ | { | ||
+ | _message.setStatus(Message.Status.TX_READ, Message.Status.TX_ERROR); | ||
+ | _message.setFlag(Message.Flag.OPENED, true); | ||
+ | } | ||
+ | |||
+ | return super.onClose(); | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | Other classes stay unchanged.<br/> | ||
+ | 9.4. Run the application, select a user from the list, and select Email Student from the menu:<br/> | ||
+ | [[Image: BB_email1.png | 300px]] | ||
+ | [[Image: BB_email2.png | 300px]]<br/> | ||
+ | 9.5. Type in the subject and some text in the body, and select Send Email from the menu:<br/> | ||
+ | [[Image: BB_email3.png | 300px]]<br/> | ||
+ | 9.6. The messageList screen is displayed showing the created email. Select Folder View from the menu to see the Outbox folder:<br/> | ||
+ | [[Image: BB_email4.png | 300px]] | ||
+ | [[Image: BB_email5.png | 300px]] |
Revision as of 13:17, 11 April 2011
9. Adding Send Email Functionality
9.1. Add the Send email option to the menu and implement it:
// Send email to student
ImageMenuItem emailItem = new ImageMenuItem("Email Student", 400, 4, MENU_EMAIL);
emailItem.setCommand(new Command(new CommandHandler()
{
public void execute(ReadOnlyCommandMetadata metadata, Object context)
{
Student student = (Student) _keywordFilterField.getSelectedElement();
if (student != null)
{
try
{
ComposeScreen composeScreen = new ComposeScreen(null, _store, student.getEmail());
pushScreen(composeScreen);
}
catch(Exception ex)
{
Dialog.alert("composing screen: " + ex.getMessage());
}
}
}
}));
_mainScreen.addMenuItem(emailItem);
9.2 Add four new Screen classes and a helper class to the project:
-
ComposeScreen.java
- start screen where email is composed -
MessageScreen.java
- screen showing the email -
MessagesViewScreen.java
- to view the created email -
MessageListField.java
- handles the drawing of the list of messages -
FoldersViewScreen.java
- view emails in the Outbox folder
The main functionality is taken from the Sample provided with blackberry plug-in: BlackBerryMailDemo
9.3. Following is the modified code to accommodate the application:
ComposeScreen.java
package cs.ecl.team1.project;
import java.util.*;
import net.rim.blackberry.api.mail.*;
import net.rim.device.api.command.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.util.*;
/**
* The ComposeScreen is a screen which displays either a new or saved message.
* It adds the functionality of saving and sending messages to its parent class,
* MessageScreen.
*/
public final class ComposeScreen extends MessageScreen
{
private static final int FIRST = 0;
private static final int SEND_MENU_ITEM_INDEX = 0;
private Store _store;
private MessagesViewScreen _messagesViewScreen;
/**
* Creates a new ComposeScreen object
* @param message A message in the process of being composed, or null if a new message is to be composed
* @param store The message store for this application
*/
public ComposeScreen(Message message, Store store, String email)
{
super(message, true, email);
_store = store;
_messagesViewScreen = new MessagesViewScreen();
// If a new message is to be created, indicate this in the title
if( message == null )
{
setTitle("New Message");
}
// Create and add menu items specific to the Compose action (addTo,
// addBcc, addCc, etc...).
AddHeaderFieldAction addToMenuItem = new AddHeaderFieldAction(Message.RecipientType.TO, "Add To: ", "To: ");
AddHeaderFieldAction addCcMenuItem = new AddHeaderFieldAction(Message.RecipientType.CC, "Add Cc: ", "Cc: ");
AddHeaderFieldAction addBccMenuItem = new AddHeaderFieldAction(Message.RecipientType.BCC, "Add Bcc: ", "Bcc: ");
// MenuItem to save a message
MenuItem saveMenuItem = new MenuItem(new StringProvider("Save Message"), 0x230020, 1);
saveMenuItem.setCommand(new Command(new CommandHandler()
{
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
*/
public void execute(ReadOnlyCommandMetadata metadata, Object context)
{ // If the save is completed, then discard this screen
if( onSave() )
{
close();
}
else
// If the message could not be saved, alert the user
{
UiApplication.getUiApplication().invokeLater(new Runnable()
{
public void run()
{
Dialog.alert("Message could not be saved");
}
});
}
}
}));
// MenuItem to send a message
MenuItem sendMenuItem = new MenuItem(new StringProvider("Send Message"), 0x230010, 0);
sendMenuItem.setCommand(new Command(new CommandHandler()
{
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
*/
public void execute(ReadOnlyCommandMetadata metadata, Object context)
{ // If the save is completed, then discard this screen
try
{
_message = getMessage();
if( _message != null )
{
// Send the message
Transport.send(_message);
UiApplication.getUiApplication().pushScreen(_messagesViewScreen);
close();
}
}
catch( MessagingException e )
{
ViewStudentApp.errorDialog("Transport.send(Message) threw " + e.toString());
}
}
}));
addMenuItem(sendMenuItem);
addMenuItem(saveMenuItem);
addMenuItem(addToMenuItem);
addMenuItem(addCcMenuItem);
addMenuItem(addBccMenuItem);
}
/**
* Overrides MessageScreen.displayMessage(). The message's 'sent' properties
* are not displayed since the message is still in the process of editing.
*/
void displayMessage(String email)
{
// If the message does not exist then compose a new message
if( _message == null )
{
// Add a To line
EditField toField = new EditField("To: ", email, 40, BasicEditField.FILTER_EMAIL);
addTextFieldToTableAndScreen(toField, Message.RecipientType.TO);
// Add a subject line
EditField subjectField = new EditField("Subject: ", "");
addTextFieldToTableAndScreen(subjectField, SUBJECT);
// Add a separator between the body and the headers
add(new SeparatorField());
// Add a body field
EditField bodyField = new EditField();
addTextFieldToTableAndScreen(bodyField, BODY);
}
else
// The message exists so display it
{
displayHeader();
add(new SeparatorField());
displayMessageBody();
}
}
/**
* Gets a message for sending or saving
* @return A new message
*/
Message getMessage()
{
// Find an outbox folder and use it to construct a new message
Folder outbox = _store.findFolder("Outbox")[ FIRST ];
Message message = new Message(outbox);
// Add all the current headers
for( int keyNo = 0; keyNo < HEADER_KEYS.length; keyNo++ )
{
Vector fieldsByType = (Vector) _fieldTable.get(HEADER_KEYS[ keyNo ]);
if( fieldsByType != null )
{
// Build a vector of all the addresses
Vector addressVector = new Vector();
int size = fieldsByType.size();
for( int fieldNo = 0; fieldNo < size; fieldNo++ )
{
TextField addressField = (TextField) fieldsByType.elementAt(fieldNo);
// Try to create a new address object wrapping the email
// address and add it to the address vector.
try
{
addressVector.addElement(new Address(addressField.getText(), ""));
}
catch( AddressException e ) // Invalid address
{
ViewStudentApp.errorDialog("Address(String, String) threw " + e.toString());
}
}
// Dump the vector of addresses into an array to send the message
Address[] addresses = new Address[ addressVector.size() ];
addressVector.copyInto(addresses);
// Try to add the addresses to the message's list of recipients
try
{
message.addRecipients(HEADER_KEYS[ keyNo ], addresses);
}
catch( MessagingException e )
{
ViewStudentApp.errorDialog("Message#addRecipients(int, Address[]) threw " + e.toString());
}
}
}
// Add the subject
Vector subjectFields = (Vector) _fieldTable.get(SUBJECT);
TextField subjectField = (TextField) subjectFields.elementAt(FIRST);
if( subjectFields != null && subjectFields.size() > 0 )
{
message.setSubject(subjectField.getText());
}
// Add the body by adding all the body fields into one multipart
Vector bodyFields = (Vector) _fieldTable.get(BODY);
if( bodyFields != null )
{
int size = bodyFields.size();
Multipart content = new Multipart();
for( int fieldNo = 0; fieldNo < size; fieldNo++ )
{
TextField body = (TextField) bodyFields.elementAt(fieldNo);
content.addBodyPart(new TextBodyPart(content, body.getText()));
}
try
{
message.setContent(content);
}
catch( MessagingException e )
{
ViewStudentApp.errorDialog("Message#setContent(Object) threw " + e.toString());
}
}
else
{
ViewStudentApp.errorDialog("Error: no body field available");
return null;
}
// Set the date
message.setSentDate(Calendar.getInstance().getTime());
return message;
}
/**
* @see net.rim.device.api.ui.Screen#onSave()
*/
protected boolean onSave()
{
// Save the message to the outbox
try
{
Message newMessage = getMessage();
if( newMessage != null )
{
// Retrieve an outbox to save the message in
Store store = Session.waitForDefaultSession().getStore();
Folder[] allOutboxFolders = store.list(Folder.OUTBOX);
Folder outbox = null;
for( int i = allOutboxFolders.length - 1; i >= 0 ; --i )
{
if( allOutboxFolders[ i ].getParent().getName().startsWith("Mailbox") )
{
outbox = allOutboxFolders[ i ];
break;
}
}
// Save the new message and replace the old one if it exists
outbox.appendMessage(newMessage);
if( _message != null )
{
outbox.deleteMessage(_message, true);
}
_message = newMessage;
// Set the status to composing and flag that it has been saved
_message.setStatus(Message.Status.TX_COMPOSING, Message.Status.TX_ERROR);
_message.setFlag(Message.Flag.SAVED, true);
return true;
}
return false;
}
catch( MessagingException e )
{
return false;
}
}
/**
* Make "Send" the default menu item.
*
* @see net.rim.device.api.ui.container.MainScreen#makeMenu(Menu,int)
*/
protected void makeMenu(Menu menu, int instance)
{
super.makeMenu(menu, instance);
// menu.setDefault(SEND_MENU_ITEM_INDEX);
}
/**
* This class is responsible for adding the various header fields to the
* compose screen (To, Bcc, CC).
*/
private final class AddHeaderFieldAction extends MenuItem
{
private String _fieldLabelText;
private int _headerType;
/**
* Constructs a menu item which adds a header of a specified type to the
* compose screen.
*
* @param headerType One of the Message.RecipientType fields
* @param menuItemText String to use for the menu item
* @param fieldLabelText String to use for the label of this field
*/
AddHeaderFieldAction(int headerType, String menuItemText, String fieldLabelText)
{
super(new StringProvider(menuItemText), 0x240010, 2);
_fieldLabelText = fieldLabelText;
_headerType = headerType;
this.setCommand(new Command(new CommandHandler()
{
/**
* Adds a new header field to the message. The field is placed so that
* the header types are grouped together and the most recently added one
* is closest to the bottom.
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
*/
public void execute(ReadOnlyCommandMetadata metadata, Object context)
{
EditField newField = new EditField(_fieldLabelText, "");
// Find out where the last field of this type was added to the
// screen.
Vector fieldsByType = (Vector) _fieldTable.get(_headerType);
int lastInsertedIndex;
if( fieldsByType == null )
{
// If a field of _headerType was not made yet, then create the
// vector which contains all of the fields of _headerType.
fieldsByType = new Vector();
_fieldTable.put(_headerType, fieldsByType);
lastInsertedIndex = getIndexForNewFieldType();
}
else
{
lastInsertedIndex = getIndexOfLastFieldOfType(_headerType);
}
// Add the new field to both the screen and the vector keeping track
// of all the fields of the same type.
ComposeScreen.this.insert(newField, lastInsertedIndex + 1);
fieldsByType.addElement(newField);
newField.setFocus();
}
}));
}
/**
* Given the existing field type of this instance of the class,
* determine where to place new header fields in the screen.
*
* @return The index at which to insert the new header field
*/
private int getIndexForNewFieldType()
{
// Note: we don't handle TO here since there ALWAYS must be one TO
// field.
switch( _headerType )
{
// Find the last TO field and use the next index as the
// insertion point.
case Message.RecipientType.CC:
return getIndexOfLastFieldOfType(Message.RecipientType.TO);
// Try to find the last CC field and use it as the next
// insertion point. If no CC field exists then find the last
// TO field and use its index as the insertion point.
case Message.RecipientType.BCC:
int index = getIndexOfLastFieldOfType(Message.RecipientType.CC);
if( index == -1 )
return getIndexOfLastFieldOfType(Message.RecipientType.TO);
return index;
default:
throw new IllegalStateException("Mail Demo: Unrecognized recipient type");
}
}
/**
* Retrieves the index of the last field added of a specified type.
*
* @param type The type of header field to retrieve the last index of
* @return The index of the most recently added field of the specified
* type, -1 if a field of the specified type has not been added
* yet.
*/
private int getIndexOfLastFieldOfType(int type)
{
Vector fields = (Vector) _fieldTable.get(type);
if( fields == null )
{
return -1;
}
Field field = (Field) fields.lastElement();
return field.getIndex();
}
}
}
MessageScreen.java
package cs.ecl.team1.project;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.util.IntHashtable;
import javax.microedition.pim.Contact;
import java.util.*;
import net.rim.blackberry.api.mail.*;
/**
* The MessageScreen class allows a user to view a selected message and
* edit the message if the screen is marked editable. It manages the different
* TextFields using a hashtable where the type of information that is held
* in a given TextField is the key while the value is a Vector of TextFields
* associated with that information type. This class supports displaying
* plain text, Mime, supported and unsupported attachments and pdap contacts.
*/
public class MessageScreen extends MainScreen
{
// Constants
public final static String NO_SUBJECT = "<No Subject>";
public final static String UNKNOWN_NAME = "<?>";
protected final static int SUBJECT = 0;
protected final static int BODY = 1;
protected final static int INFO = 2;
protected final static int[] HEADER_KEYS = { Message.RecipientType.TO, Message.RecipientType.CC, Message.RecipientType.BCC };
protected final static String[] HEADER_NAMES = { "To: ", "Cc: ", "Bcc: " };
private final static int MAX_CHARS = 128;
protected IntHashtable _fieldTable;
protected Message _message;
private boolean _editable;
/**
* Creates a new MessageScreen object
* @param message The message to display
* @param editable True is the message is editable, otherwise false
*/
public MessageScreen(Message message, boolean editable, String email)
{
_fieldTable = new IntHashtable();
_editable = editable;
// Set the message and display its subject as the title if the
// message exists.
_message = message;
if( _message != null )
setTitle(_message.getSubject());
displayMessage(email);
}
/**
* Displays the message
*/
void displayMessage(String email)
{
displayMessageInformation();
add(new SeparatorField());
displayHeader();
add(new SeparatorField());
displayMessageBody();
}
/**
* Displays information about the message's send and recieve properties
*/
protected void displayMessageInformation()
{
// Add a field describing the source service
ServiceConfiguration sc = _message.getFolder().getStore().getServiceConfiguration();
EditField service = new EditField("Service: ", sc.getName(), MAX_CHARS, EditField.READONLY | EditField.NON_FOCUSABLE);
addTextFieldToTableAndScreen(service, INFO);
// Add the folder field
EditField folder = new EditField("Folder: ", _message.getFolder().getName(), MAX_CHARS, EditField.READONLY
| EditField.NON_FOCUSABLE);
addTextFieldToTableAndScreen(folder, INFO);
// Add the status of the message
String statusString = getStatusString(_message);
EditField status = new EditField("Status: ", statusString, MAX_CHARS, EditField.READONLY | EditField.NON_FOCUSABLE);
addTextFieldToTableAndScreen(status, INFO);
}
/**
* Displays information about the destination and source of the message as
* well as its subject.
*/
protected void displayHeader()
{
// Assign the appropriate EditField style property
long editableStyle = _editable ? EditField.EDITABLE : EditField.READONLY;
// Display the headers (To:, Cc:, Bcc:)
for( int key = 0; key < HEADER_KEYS.length; key++ )
{
try
{
Address[] addresses = _message.getRecipients(HEADER_KEYS[ key ]);
for( int index = 0; index < addresses.length; index++ )
{
// Retrieve the name
String name = addresses[ index ].getName();
if( name == null || name.length() == 0 )
{
name = addresses[ index ].getAddr();
}
// Create the edit field, associate the address to the field
// and add it to the screen and collection of fields.
EditField headerField = new EditField(HEADER_NAMES[ key ], name, EditField.DEFAULT_MAXCHARS, editableStyle);
headerField.setCookie(addresses[ index ]);
addTextFieldToTableAndScreen(headerField, HEADER_KEYS[ key ]);
}
}
catch( MessagingException e )
{
ViewStudentApp.errorDialog("Error: could not retrieve message header.");
close();
}
}
// Display the 'Sent' date if it is available
Date sent = _message.getSentDate();
if( sent != null )
{
EditField sentDate = new EditField("Sent: ", Util.getDateAsString(sent), EditField.DEFAULT_MAXCHARS, EditField.READONLY
| EditField.NON_FOCUSABLE);
// Change the label to "Saved: " if the message hasn't been sent yet
if( _message.getStatus() == Message.Status.TX_COMPOSING )
sentDate.setLabel("Saved: ");
add(sentDate);
}
// Display the subject field
String subject = _message.getSubject();
if( subject == null )
subject = NO_SUBJECT;
EditField subjectField = new EditField("Subject: ", subject, EditField.DEFAULT_MAXCHARS, editableStyle);
addTextFieldToTableAndScreen(subjectField, SUBJECT);
}
/**
* Displays the message body
*/
protected void displayMessageBody()
{
// Retrieve the parent of the message body
Object obj = _message.getContent();
Multipart parent = null;
if( obj instanceof MimeBodyPart || obj instanceof TextBodyPart )
{
BodyPart bp = (BodyPart) obj;
parent = bp.getParent();
}
else
{
parent = (Multipart) obj;
}
// Display the message body
String mpType = parent.getContentType();
if( mpType.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING)
|| mpType.equals(BodyPart.ContentType.TYPE_MULTIPART_MIXED_STRING) )
{
displayMultipart(parent);
}
// Ensure there is at least one body field if nothing was displayed
Vector bodyVector = (Vector) _fieldTable.get(BODY);
if( bodyVector == null || bodyVector.size() == 0 )
{
if( _editable )
{
addTextFieldToTableAndScreen(new EditField("", ""), BODY);
}
else
{
addTextFieldToTableAndScreen(new RichTextField(""), BODY);
}
}
}
/**
* Processes a multi-part message by displaying its body parts. Text body
* parts are displayed before attachments and if a multi body part is
* encountered, then it is processed through recursion by calling this method
* on it.
*
* @param multipart The multi-part to display
* @param editable True if this multi-part is editable
*/
protected void displayMultipart(Multipart multipart)
{
// This vector stores fields which are to be displayed only after all
// of the body fields are displayed. (Attachments and Contacts).
Vector delayedFields = new Vector();
// Process each part of the multi-part, taking the appropriate action
// depending on the part's type. This loop should: display text and
// html body parts, recursively display multi-parts and store
// attachments and contacts to display later.
for( int index = 0; index < multipart.getCount(); index++ )
{
BodyPart bodyPart = multipart.getBodyPart(index);
// If this body part is text then display all of it
if( bodyPart instanceof TextBodyPart )
{
TextBodyPart textBodyPart = (TextBodyPart) bodyPart;
// If there are missing parts of the text, try to retrieve the
// rest of it.
if( textBodyPart.hasMore() )
{
try
{
Transport.more(textBodyPart, true);
}
catch( Exception e )
{
ViewStudentApp.errorDialog("Transport.more(BodyPart, boolean) threw " + e.toString());
}
}
String plainText = (String) textBodyPart.getContent();
// Display the plain text, using an EditField if the message is
// editable or a RichTextField if it is not editable. Note: this
// does not add any empty fields.
if( plainText.length() != 0 )
{
if( _editable )
{
addTextFieldToTableAndScreen(new EditField("", plainText), BODY);
}
else
{
addTextFieldToTableAndScreen(new RichTextField(plainText), BODY);
}
}
}
else if( bodyPart instanceof MimeBodyPart )
{
MimeBodyPart mimeBodyPart = (MimeBodyPart) bodyPart;
// If the content is text then display it
String contentType = mimeBodyPart.getContentType();
if( contentType.startsWith(BodyPart.ContentType.TYPE_TEXT_HTML_STRING) )
{
Object obj = mimeBodyPart.getContent();
if( obj != null )
{
String htmlText = new String((byte[]) obj);
addTextFieldToTableAndScreen(new RichTextField(htmlText), BODY);
}
}
else if( contentType.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING) )
{
// If the body part is a multi-part and it has the the
// content type of TYPE_MULTIPART_ALTERNATIVE_STRING, then
// recursively display the multi-part.
Object obj = mimeBodyPart.getContent();
if( obj instanceof Multipart )
{
Multipart childMultipart = (Multipart) obj;
String childMultipartType = childMultipart.getContentType();
if( childMultipartType.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING) )
{
displayMultipart(childMultipart);
}
}
}
}
else if( bodyPart instanceof SupportedAttachmentPart || bodyPart instanceof UnsupportedAttachmentPart )
{
// Extract the content type and name from the attachments
String contentType = bodyPart.getContentType();
String name;
if( bodyPart instanceof UnsupportedAttachmentPart )
{
UnsupportedAttachmentPart uap = (UnsupportedAttachmentPart) bodyPart;
name = uap.getName();
}
else // The bodyPart is a SupportedAttachmentPart
{
SupportedAttachmentPart sap = (SupportedAttachmentPart) bodyPart;
name = sap.getName();
}
// Format the content type and name to display and store
// the field.
StringBuffer sb = new StringBuffer(contentType.length() + name.length() + 2);
sb.append(contentType);
sb.append('[');
sb.append(name);
sb.append(']');
delayedFields.addElement(new RichTextField(sb.toString()));
}
else if( bodyPart instanceof PDAPContactAttachmentPart )
{
Contact contact = (Contact) bodyPart.getContent();
// Build the contact name
StringBuffer sb = new StringBuffer("Contact: ");
if( contact.countValues(Contact.NAME) > 0 )
{
String[] name = contact.getStringArray(Contact.NAME, 0);
if( name[ Contact.NAME_PREFIX ] != null )
{
sb.append(name[ Contact.NAME_PREFIX ]);
sb.append(' ');
}
if( name[ Contact.NAME_GIVEN ] != null )
{
sb.append(name[ Contact.NAME_GIVEN ]);
sb.append(' ');
}
if( name[ Contact.NAME_FAMILY ] != null )
{
sb.append(name[ Contact.NAME_FAMILY ]);
}
// Trim the last space of the name if it exists
int lastChar = sb.length() - 1;
if( sb.charAt(lastChar) == ' ' )
sb.deleteCharAt(lastChar);
}
else
{
sb.append(UNKNOWN_NAME);
}
// Create the contact attachment field and store it
RichTextField contactAttachment = new RichTextField(sb.toString());
contactAttachment.setCookie(contact);
delayedFields.addElement(contactAttachment);
}
}
// Now that the body parts have been displayed, display the queued
// fields while separating them by inserting a separator field.
for( int index = 0; index < delayedFields.size(); index++ )
{
add(new SeparatorField());
addTextFieldToTableAndScreen((TextField) delayedFields.elementAt(index), BODY);
}
}
/**
* Compiles the status of a message into a readable string.
*
* @param message The message whose status is to be compiled into a string
* @return The string displaying the status of the message
*/
public static String getStatusString(Message message)
{
StringBuffer statusStrBuffer = new StringBuffer();
// Add any errors to the status string if it applies
int status = message.getStatus();
if( status == Message.Status.RX_ERROR )
{
statusStrBuffer.append("RX ERROR, ");
}
if( status == Message.Status.TX_GENERAL_FAILURE )
{
statusStrBuffer.append("RX ERROR, ");
}
if( status == Message.Status.TX_ERROR )
{
statusStrBuffer.append("TX ERROR, ");
}
// Use the flags to add any message statuses
int flags = message.getFlags();
if( 0 != (flags & Message.Flag.OPENED) )
{
statusStrBuffer.append("Opened, ");
}
if( 0 != (flags & Message.Flag.SAVED) )
{
statusStrBuffer.append("Saved, ");
}
if( 0 != (flags & Message.Flag.FILED) )
{
statusStrBuffer.append("Filed, ");
}
// Check if the message has a high or low priority
byte messagePriority = message.getPriority();
if( messagePriority == Message.Priority.HIGH )
{
statusStrBuffer.append("High Priority, ");
}
else if( messagePriority == Message.Priority.LOW )
{
statusStrBuffer.append("Low Priority, ");
}
// If there are any characters in the status string then delete the last
// two characters if there are any characters to delete. Should be
// either ", " or " ".
statusStrBuffer.delete(statusStrBuffer.length() - 2, statusStrBuffer.length());
return statusStrBuffer.toString();
}
/**
* Add a new field to the hashtable of TextFields and to the screen
*
* @param field The field to add
* @param type The type of field to add
*/
protected void addTextFieldToTableAndScreen(TextField field, int type)
{
Vector fieldsByType = (Vector) _fieldTable.get(type);
// If the vector of fields associated with the type is not made yet,
// initialize one and put it into the fields collection.
if( fieldsByType == null )
{
fieldsByType = new Vector(1);
_fieldTable.put(type, fieldsByType);
}
fieldsByType.addElement(field);
add(field);
}
/**
* @see net.rim.device.api.ui.Screen#onClose()
*/
public boolean onClose()
{
// If the message status is "received", mark it "read"
if( _message != null && _message.getStatus() == Message.Status.RX_RECEIVED )
{
_message.setStatus(Message.Status.TX_READ, Message.Status.TX_ERROR);
_message.setFlag(Message.Flag.OPENED, true);
}
return super.onClose();
}
}
Other classes stay unchanged.
9.4. Run the application, select a user from the list, and select Email Student from the menu:
9.5. Type in the subject and some text in the body, and select Send Email from the menu:
9.6. The messageList screen is displayed showing the created email. Select Folder View from the menu to see the Outbox folder: