This commit is contained in:
ashley willis 2011-11-14 13:16:19 -08:00 committed by Andrew Chen
parent b1de711851
commit 1012ad56dd
2 changed files with 322 additions and 193 deletions

View File

@ -1,153 +1,161 @@
package com.fsck.k9.activity; package com.fsck.k9.activity;
import java.io.Serializable; import java.io.Serializable;
/** /**
* <p>Represents an HTML document with an insertion point for placing a reply. The quoted * <p>Represents an HTML document with an insertion point for placing a reply. The quoted
* document may have been modified to make it suitable for insertion. The modified quoted * document may have been modified to make it suitable for insertion. The modified quoted
* document should be used in place of the original document.</p> * document should be used in place of the original document.</p>
* *
* <p>Changes to the user-generated inserted content should be done with {@link * <p>Changes to the user-generated inserted content should be done with {@link
* #setUserContent(String)}.</p> * #setUserContent(String)}.</p>
* *
* TODO: This container should also have a text part, along with its insertion point. Or maybe a generic InsertableContent and maintain one each for Html and Text? * TODO: This container should also have a text part, along with its insertion point. Or maybe a generic InsertableContent and maintain one each for Html and Text?
*/ */
class InsertableHtmlContent implements Serializable { class InsertableHtmlContent implements Serializable {
private static final long serialVersionUID = 2397327034L; private static final long serialVersionUID = 2397327034L;
// Default to a headerInsertionPoint at the beginning of the message. // Default to a headerInsertionPoint at the beginning of the message.
private int headerInsertionPoint = 0; private int headerInsertionPoint = 0;
private int footerInsertionPoint = 0; private int footerInsertionPoint = 0;
// Quoted message, if any. headerInsertionPoint refers to a position in this string. // Quoted message, if any. headerInsertionPoint refers to a position in this string.
private StringBuilder quotedContent = new StringBuilder(); private StringBuilder quotedContent = new StringBuilder();
// User content (typically their reply or comments on a forward) // User content (typically their reply or comments on a forward)
private StringBuilder userContent = new StringBuilder(); private StringBuilder userContent = new StringBuilder();
// Where to insert the content. Default to top posting. // Where to insert the content. Default to top posting.
private InsertionLocation insertionLocation = InsertionLocation.BEFORE_QUOTE; private InsertionLocation insertionLocation = InsertionLocation.BEFORE_QUOTE;
/** /**
* Defines where user content should be inserted, either before or after quoted content. * Defines where user content should be inserted, either before or after quoted content.
*/ */
public enum InsertionLocation { public enum InsertionLocation {
BEFORE_QUOTE, AFTER_QUOTE BEFORE_QUOTE, AFTER_QUOTE
} }
public void setHeaderInsertionPoint(int headerInsertionPoint) { public void setHeaderInsertionPoint(int headerInsertionPoint) {
this.headerInsertionPoint = headerInsertionPoint; this.headerInsertionPoint = headerInsertionPoint;
} }
public void setFooterInsertionPoint(int footerInsertionPoint) { public void setFooterInsertionPoint(int footerInsertionPoint) {
this.footerInsertionPoint = footerInsertionPoint; this.footerInsertionPoint = footerInsertionPoint;
} }
/** /**
* Get the quoted content. * Get the quoted content.
* @return Quoted content. * @return Quoted content.
*/ */
public String getQuotedContent() { public String getQuotedContent() {
return quotedContent.toString(); return quotedContent.toString();
} }
/** /**
* Set the quoted content. The insertion point should be set against this content. * Set the quoted content. The insertion point should be set against this content.
* @param content * @param content
*/ */
public void setQuotedContent(StringBuilder content) { public void setQuotedContent(StringBuilder content) {
this.quotedContent = content; this.quotedContent = content;
} }
/** /**
* <p>Insert something into the quoted content header. This is typically used for inserting * <p>Insert something into the quoted content header. This is typically used for inserting
* reply/forward headers into the quoted content rather than inserting the user-generated reply * reply/forward headers into the quoted content rather than inserting the user-generated reply
* content.</p> * content.</p>
* *
* <p>Subsequent calls to {@link #insertIntoQuotedHeader(String)} will <b>prepend</b> text onto any * <p>Subsequent calls to {@link #insertIntoQuotedHeader(String)} will <b>prepend</b> text onto any
* existing header and quoted content.</p> * existing header and quoted content.</p>
* @param content Content to add. * @param content Content to add.
*/ */
public void insertIntoQuotedHeader(final String content) { public void insertIntoQuotedHeader(final String content) {
quotedContent.insert(headerInsertionPoint, content); quotedContent.insert(headerInsertionPoint, content);
// Update the location of the footer insertion point. // Update the location of the footer insertion point.
footerInsertionPoint += content.length(); footerInsertionPoint += content.length();
} }
/** /**
* <p>Insert something into the quoted content footer. This is typically used for inserting closing * <p>Insert something into the quoted content footer. This is typically used for inserting closing
* tags of reply/forward headers rather than inserting the user-generated reply content.</p> * tags of reply/forward headers rather than inserting the user-generated reply content.</p>
* *
* <p>Subsequent calls to {@link #insertIntoQuotedFooter(String)} will <b>append</b> text onto any * <p>Subsequent calls to {@link #insertIntoQuotedFooter(String)} will <b>append</b> text onto any
* existing footer and quoted content.</p> * existing footer and quoted content.</p>
* @param content Content to add. * @param content Content to add.
*/ */
public void insertIntoQuotedFooter(final String content) { public void insertIntoQuotedFooter(final String content) {
quotedContent.insert(footerInsertionPoint, content); quotedContent.insert(footerInsertionPoint, content);
// Update the location of the footer insertion point to the end of the inserted content. // Update the location of the footer insertion point to the end of the inserted content.
footerInsertionPoint += content.length(); footerInsertionPoint += content.length();
} }
/** /**
* Remove all quoted content. * Remove all quoted content.
*/ */
public void clearQuotedContent() { public void clearQuotedContent() {
quotedContent.setLength(0); quotedContent.setLength(0);
footerInsertionPoint = 0; footerInsertionPoint = 0;
headerInsertionPoint = 0; headerInsertionPoint = 0;
} }
/** /**
* Set the inserted content to the specified content. Replaces anything currently in the * Set the inserted content to the specified content. Replaces anything currently in the
* inserted content buffer. * inserted content buffer.
* @param content * @param content
*/ */
public void setUserContent(final String content) { public void setUserContent(final String content) {
userContent = new StringBuilder(content); userContent = new StringBuilder(content);
} }
/** /**
* Configure where user content should be inserted, either before or after the quoted content. * Configure where user content should be inserted, either before or after the quoted content.
* @param insertionLocation Where to insert user content. * @param insertionLocation Where to insert user content.
*/ */
public void setInsertionLocation(final InsertionLocation insertionLocation) { public void setInsertionLocation(final InsertionLocation insertionLocation) {
this.insertionLocation = insertionLocation; this.insertionLocation = insertionLocation;
} }
/** /**
* Fetch the insertion point based upon the quote style. * Fetch the insertion point based upon the quote style.
* @return Insertion point * @return Insertion point
*/ */
public int getInsertionPoint() { public int getInsertionPoint() {
if (insertionLocation == InsertionLocation.BEFORE_QUOTE) { if (insertionLocation == InsertionLocation.BEFORE_QUOTE) {
return headerInsertionPoint; return headerInsertionPoint;
} else { } else {
return footerInsertionPoint; return footerInsertionPoint;
} }
} }
/** /**
* Build the composed string with the inserted and original content. * Get the footer insertion point.
* @return Composed string. * @return Footer insertion point
*/ */
@Override public int getFooterInsertionPoint() {
public String toString() { return footerInsertionPoint;
final int insertionPoint = getInsertionPoint(); }
// Inserting and deleting was twice as fast as instantiating a new StringBuilder and
// using substring() to build the new pieces. /**
String result = quotedContent.insert(insertionPoint, userContent.toString()).toString(); * Build the composed string with the inserted and original content.
quotedContent.delete(insertionPoint, insertionPoint + userContent.length()); * @return Composed string.
return result; */
} @Override
public String toString() {
/** final int insertionPoint = getInsertionPoint();
* Return debugging information for this container. // Inserting and deleting was twice as fast as instantiating a new StringBuilder and
* @return Debug string. // using substring() to build the new pieces.
*/ String result = quotedContent.insert(insertionPoint, userContent.toString()).toString();
public String toDebugString() { quotedContent.delete(insertionPoint, insertionPoint + userContent.length());
return "InsertableHtmlContent{" + return result;
"headerInsertionPoint=" + headerInsertionPoint + }
", footerInsertionPoint=" + footerInsertionPoint +
", insertionLocation=" + insertionLocation + /**
", quotedContent=" + quotedContent + * Return debugging information for this container.
", userContent=" + userContent + * @return Debug string.
", compiledResult=" + toString() + */
'}'; public String toDebugString() {
} return "InsertableHtmlContent{" +
} "headerInsertionPoint=" + headerInsertionPoint +
", footerInsertionPoint=" + footerInsertionPoint +
", insertionLocation=" + insertionLocation +
", quotedContent=" + quotedContent +
", userContent=" + userContent +
", compiledResult=" + toString() +
'}';
}
}

View File

@ -207,6 +207,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private boolean mSourceProcessed = false; private boolean mSourceProcessed = false;
private MessageFormat mMessageFormat; private MessageFormat mMessageFormat;
private QuoteStyle mQuoteStyle;
private boolean mDraftNeedsSaving = false; private boolean mDraftNeedsSaving = false;
private boolean mPreventDraftSaving = false; private boolean mPreventDraftSaving = false;
@ -534,6 +535,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
mMessageFormat = mAccount.getMessageFormat(); mMessageFormat = mAccount.getMessageFormat();
mReadReceipt = mAccount.isMessageReadReceiptAlways(); mReadReceipt = mAccount.isMessageReadReceiptAlways();
mQuoteStyle = mAccount.getQuoteStyle();
if (!mSourceMessageProcessed) { if (!mSourceMessageProcessed) {
updateFrom(); updateFrom();
@ -907,6 +909,17 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
* @param isDraft If we should build a message that will be saved as a draft (as opposed to sent). * @param isDraft If we should build a message that will be saved as a draft (as opposed to sent).
*/ */
private TextBody buildText(boolean isDraft) { private TextBody buildText(boolean isDraft) {
return buildText(isDraft, mMessageFormat);
}
/*
* Build the Body that will contain the text of the message. We'll decide where to
* include it later. Draft messages are treated somewhat differently in that signatures are not
* appended and HTML separators between composed text and quoted text are not added.
* @param isDraft If we should build a message that will be saved as a draft (as opposed to sent).
* @param messageFormat Set MessageFormat to build.
*/
private TextBody buildText(boolean isDraft, MessageFormat messageFormat) {
boolean replyAfterQuote = false; boolean replyAfterQuote = false;
String action = getIntent().getAction(); String action = getIntent().getAction();
if (mAccount.isReplyAfterQuote() && if (mAccount.isReplyAfterQuote() &&
@ -926,10 +939,13 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// Handle HTML separate from the rest of the text content. HTML mode doesn't allow signature after the quoted // Handle HTML separate from the rest of the text content. HTML mode doesn't allow signature after the quoted
// text, nor does it allow reply after quote. Users who want that functionality will need to stick with text // text, nor does it allow reply after quote. Users who want that functionality will need to stick with text
// mode. // mode.
if (mMessageFormat == MessageFormat.HTML) { if (messageFormat == MessageFormat.HTML) {
// Add the signature. // Place the signature immediately after the reply.
if (!isDraft) { if (!isDraft) {
text = appendSignature(text); if (mQuoteStyle == QuoteStyle.HEADER || replyAfterQuote || mAccount.isSignatureBeforeQuotedText()) {
Log.d("ASH", "appending signature after new content");
text = appendSignature(text);
}
} }
text = HtmlConverter.textToHtmlFragment(text); text = HtmlConverter.textToHtmlFragment(text);
// Insert it into the existing content object. // Insert it into the existing content object.
@ -943,7 +959,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// location. We only add the extra separators when we're sending, that way when we // location. We only add the extra separators when we're sending, that way when we
// load a draft, we don't have to know the length of the separators to remove them // load a draft, we don't have to know the length of the separators to remove them
// before editing. // before editing.
if (mAccount.getQuoteStyle() == QuoteStyle.PREFIX && replyAfterQuote) { if (mQuoteStyle == QuoteStyle.PREFIX && replyAfterQuote) {
mQuotedHtmlContent.setInsertionLocation(InsertableHtmlContent.InsertionLocation.AFTER_QUOTE); mQuotedHtmlContent.setInsertionLocation(InsertableHtmlContent.InsertionLocation.AFTER_QUOTE);
if (!isDraft) { if (!isDraft) {
text = "<br clear=\"all\">" + text; text = "<br clear=\"all\">" + text;
@ -955,6 +971,13 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} }
} }
// Place signature immediately after quote.
if (!isDraft) {
if (mQuoteStyle == QuoteStyle.PREFIX && !replyAfterQuote && !mAccount.isSignatureBeforeQuotedText()) {
mQuotedHtmlContent.insertIntoQuotedFooter(getSignatureHtml());
}
}
mQuotedHtmlContent.setUserContent(text); mQuotedHtmlContent.setUserContent(text);
// All done. Build the body. // All done. Build the body.
@ -970,20 +993,21 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
body.setComposedMessageOffset(0); body.setComposedMessageOffset(0);
return body; return body;
} }
} else if (mMessageFormat == MessageFormat.TEXT) { } else if (messageFormat == MessageFormat.TEXT) {
// Capture composed message length before we start attaching quoted parts and signatures. // Capture composed message length before we start attaching quoted parts and signatures.
Integer composedMessageLength = text.length(); Integer composedMessageLength = text.length();
Integer composedMessageOffset = 0; Integer composedMessageOffset = 0;
// Placing the signature before the quoted text does not make sense if replyAfterQuote is true. // Placing the signature before the quoted text does not make sense if replyAfterQuote is true.
if (!isDraft) { if (!isDraft) {
if (!replyAfterQuote && mAccount.isSignatureBeforeQuotedText()) { if (mQuoteStyle == QuoteStyle.HEADER ||
(!replyAfterQuote && mAccount.isSignatureBeforeQuotedText())) {
text = appendSignature(text); text = appendSignature(text);
} }
} }
if (saveQuotedText) { if (saveQuotedText) {
if (replyAfterQuote) { if (mQuoteStyle == QuoteStyle.PREFIX && replyAfterQuote) {
composedMessageOffset = mQuotedText.getText().toString().length() + "\n".length(); composedMessageOffset = mQuotedText.getText().toString().length() + "\n".length();
text = mQuotedText.getText().toString() + "\n" + text; text = mQuotedText.getText().toString() + "\n" + text;
} else { } else {
@ -994,7 +1018,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// Note: If user has selected reply after quote AND signature before quote, ignore the // Note: If user has selected reply after quote AND signature before quote, ignore the
// latter setting and append the signature at the end. // latter setting and append the signature at the end.
if (!isDraft) { if (!isDraft) {
if (replyAfterQuote || !mAccount.isSignatureBeforeQuotedText()) { if (mQuoteStyle == QuoteStyle.PREFIX &&
(replyAfterQuote || !mAccount.isSignatureBeforeQuotedText())) {
text = appendSignature(text); text = appendSignature(text);
} }
} }
@ -1002,7 +1027,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
TextBody body = new TextBody(text); TextBody body = new TextBody(text);
body.setComposedMessageLength(composedMessageLength); body.setComposedMessageLength(composedMessageLength);
body.setComposedMessageOffset(composedMessageOffset); body.setComposedMessageOffset(composedMessageOffset);
return body; return body;
} else { } else {
// Shouldn't happen. // Shouldn't happen.
@ -1048,7 +1072,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} }
// Build the body. // Build the body.
// TODO FIXME - body can be either an HTML or Text part, depending on whether we're in HTML mode or not. Should probably fix this so we don't mix up html and text parts. // TODO FIXME - body can be either an HTML or Text part, depending on whether we're in
// HTML mode or not. Should probably fix this so we don't mix up html and text parts.
TextBody body = null; TextBody body = null;
if (mPgpData.getEncryptedData() != null) { if (mPgpData.getEncryptedData() != null) {
String text = mPgpData.getEncryptedData(); String text = mPgpData.getEncryptedData();
@ -1057,6 +1082,9 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
body = buildText(isDraft); body = buildText(isDraft);
} }
// text/plain part when mMessageFormat == MessageFormat.HTML
TextBody bodyPlain = null;
final boolean hasAttachments = mAttachments.getChildCount() > 0; final boolean hasAttachments = mAttachments.getChildCount() > 0;
if (mMessageFormat == MessageFormat.HTML) { if (mMessageFormat == MessageFormat.HTML) {
@ -1066,7 +1094,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
MimeMultipart composedMimeMessage = new MimeMultipart(); MimeMultipart composedMimeMessage = new MimeMultipart();
composedMimeMessage.setSubType("alternative"); // Let the receiver select either the text or the HTML part. composedMimeMessage.setSubType("alternative"); // Let the receiver select either the text or the HTML part.
composedMimeMessage.addBodyPart(new MimeBodyPart(body, "text/html")); composedMimeMessage.addBodyPart(new MimeBodyPart(body, "text/html"));
composedMimeMessage.addBodyPart(new MimeBodyPart(new TextBody(HtmlConverter.htmlToText(body.getText())), "text/plain")); bodyPlain = buildText(isDraft, MessageFormat.TEXT);
composedMimeMessage.addBodyPart(new MimeBodyPart(bodyPlain, "text/plain"));
if (hasAttachments) { if (hasAttachments) {
// If we're HTML and have attachments, we have a MimeMultipart container to hold the // If we're HTML and have attachments, we have a MimeMultipart container to hold the
@ -1097,7 +1126,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// If this is a draft, add metadata for thawing. // If this is a draft, add metadata for thawing.
if (isDraft) { if (isDraft) {
// Add the identity to the message. // Add the identity to the message.
message.addHeader(K9.IDENTITY_HEADER, buildIdentityHeader(body)); message.addHeader(K9.IDENTITY_HEADER, buildIdentityHeader(body, bodyPlain));
} }
return message; return message;
@ -1155,6 +1184,9 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private enum IdentityField { private enum IdentityField {
LENGTH("l"), LENGTH("l"),
OFFSET("o"), OFFSET("o"),
FOOTER_OFFSET("fo"),
PLAIN_LENGTH("pl"),
PLAIN_OFFSET("po"),
MESSAGE_FORMAT("f"), MESSAGE_FORMAT("f"),
MESSAGE_READ_RECEIPT("r"), MESSAGE_READ_RECEIPT("r"),
SIGNATURE("s"), SIGNATURE("s"),
@ -1163,7 +1195,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// TODO - store a reference to the message being replied so we can mark it at the time of send. // TODO - store a reference to the message being replied so we can mark it at the time of send.
ORIGINAL_MESSAGE("m"), ORIGINAL_MESSAGE("m"),
CURSOR_POSITION("p"), // Where in the message your cursor was when you saved. CURSOR_POSITION("p"), // Where in the message your cursor was when you saved.
QUOTED_TEXT_MODE("q"); QUOTED_TEXT_MODE("q"),
QUOTE_STYLE("qs");
private final String value; private final String value;
@ -1181,7 +1214,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
* @return * @return
*/ */
public static IdentityField[] getIntegerFields() { public static IdentityField[] getIntegerFields() {
return new IdentityField[] { LENGTH, OFFSET }; return new IdentityField[] { LENGTH, OFFSET, FOOTER_OFFSET, PLAIN_LENGTH, PLAIN_OFFSET };
} }
} }
@ -1199,6 +1232,20 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
* @return Identity string. * @return Identity string.
*/ */
private String buildIdentityHeader(final TextBody body) { private String buildIdentityHeader(final TextBody body) {
return buildIdentityHeader(body, null);
}
/**
* Build the identity header string. This string contains metadata about a draft message to be
* used upon loading a draft for composition. This should be generated at the time of saving a
* draft.<br>
* <br>
* This is a URL-encoded key/value pair string. The list of possible values are in {@link IdentityField}.
* @param body {@link TextBody} to analyze for body length and offset.
* @param bodyPlain {@link TextBody} to analyze for body length and offset. May be null.
* @return Identity string.
*/
private String buildIdentityHeader(final TextBody body, final TextBody bodyPlain) {
Uri.Builder uri = new Uri.Builder(); Uri.Builder uri = new Uri.Builder();
if (body.getComposedMessageLength() != null && body.getComposedMessageOffset() != null) { if (body.getComposedMessageLength() != null && body.getComposedMessageOffset() != null) {
// See if the message body length is already in the TextBody. // See if the message body length is already in the TextBody.
@ -1209,6 +1256,24 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
uri.appendQueryParameter(IdentityField.LENGTH.value(), Integer.toString(body.getText().length())); uri.appendQueryParameter(IdentityField.LENGTH.value(), Integer.toString(body.getText().length()));
uri.appendQueryParameter(IdentityField.OFFSET.value(), Integer.toString(0)); uri.appendQueryParameter(IdentityField.OFFSET.value(), Integer.toString(0));
} }
if (mQuotedHtmlContent != null) {
uri.appendQueryParameter(IdentityField.FOOTER_OFFSET.value(),
Integer.toString(mQuotedHtmlContent.getFooterInsertionPoint()));
}
if (bodyPlain != null) {
if (bodyPlain.getComposedMessageLength() != null && bodyPlain.getComposedMessageOffset() != null) {
// See if the message body length is already in the TextBody.
uri.appendQueryParameter(IdentityField.PLAIN_LENGTH.value(), bodyPlain.getComposedMessageLength().toString());
uri.appendQueryParameter(IdentityField.PLAIN_OFFSET.value(), bodyPlain.getComposedMessageOffset().toString());
} else {
// If not, calculate it now.
uri.appendQueryParameter(IdentityField.PLAIN_LENGTH.value(), Integer.toString(body.getText().length()));
uri.appendQueryParameter(IdentityField.PLAIN_OFFSET.value(), Integer.toString(0));
}
}
// Save the quote style (useful for forwards).
uri.appendQueryParameter(IdentityField.QUOTE_STYLE.value(), mQuoteStyle.name());
// Save the message format for this offset. // Save the message format for this offset.
uri.appendQueryParameter(IdentityField.MESSAGE_FORMAT.value(), mMessageFormat.name()); uri.appendQueryParameter(IdentityField.MESSAGE_FORMAT.value(), mMessageFormat.name());
@ -1325,6 +1390,16 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
return text; return text;
} }
private String getSignatureHtml() {
String signature = "";
if (mIdentity.getSignatureUse()) {
signature = mSignatureView.getText().toString();
if (signature != null && !signature.contentEquals("")) {
signature = HtmlConverter.textToHtmlFragment("\n" + signature);
}
}
return signature;
}
private void sendMessage() { private void sendMessage() {
new SendMessageTask().execute(); new SendMessageTask().execute();
@ -2034,6 +2109,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} else { } else {
mSubjectView.setText(subject); mSubjectView.setText(subject);
} }
mQuoteStyle = QuoteStyle.HEADER;
// Quote the message and setup the UI. // Quote the message and setup the UI.
populateUIWithQuotedMessage(true); populateUIWithQuotedMessage(true);
@ -2146,6 +2222,19 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
Integer bodyOffset = k9identity.get(IdentityField.OFFSET) != null Integer bodyOffset = k9identity.get(IdentityField.OFFSET) != null
? Integer.parseInt(k9identity.get(IdentityField.OFFSET)) ? Integer.parseInt(k9identity.get(IdentityField.OFFSET))
: 0; : 0;
Integer bodyFooterOffset = k9identity.get(IdentityField.FOOTER_OFFSET) != null
? Integer.parseInt(k9identity.get(IdentityField.FOOTER_OFFSET))
: null;
Integer bodyPlainLength = k9identity.get(IdentityField.PLAIN_LENGTH) != null
? Integer.parseInt(k9identity.get(IdentityField.PLAIN_LENGTH))
: null;
Integer bodyPlainOffset = k9identity.get(IdentityField.PLAIN_OFFSET) != null
? Integer.parseInt(k9identity.get(IdentityField.PLAIN_OFFSET))
: null;
mQuoteStyle = k9identity.get(IdentityField.QUOTE_STYLE) != null
? QuoteStyle.valueOf(k9identity.get(IdentityField.QUOTE_STYLE))
: mAccount.getQuoteStyle();
// Always respect the user's current composition format preference, even if the // Always respect the user's current composition format preference, even if the
// draft was saved in a different format. // draft was saved in a different format.
// TODO - The current implementation doesn't allow a user in HTML mode to edit a draft that wasn't saved with K9mail. // TODO - The current implementation doesn't allow a user in HTML mode to edit a draft that wasn't saved with K9mail.
@ -2185,34 +2274,19 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
mQuotedHtmlContent.setQuotedContent(quotedHTML); mQuotedHtmlContent.setQuotedContent(quotedHTML);
// We don't know if bodyOffset refers to the header or to the footer // We don't know if bodyOffset refers to the header or to the footer
mQuotedHtmlContent.setHeaderInsertionPoint(bodyOffset); mQuotedHtmlContent.setHeaderInsertionPoint(bodyOffset);
mQuotedHtmlContent.setFooterInsertionPoint(bodyOffset); if (bodyFooterOffset != null) {
mQuotedHtmlContent.setFooterInsertionPoint(bodyFooterOffset);
} else {
mQuotedHtmlContent.setFooterInsertionPoint(bodyOffset);
}
mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null); mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null);
} }
} }
} else if (mMessageFormat == MessageFormat.TEXT) { if (bodyPlainOffset != null && bodyPlainLength != null) {
Part textPart = MimeUtility.findFirstPartByMimeType(message, "text/plain"); processSourceMessageText(message, bodyPlainOffset, bodyPlainLength, false);
if (textPart != null) {
String text = MimeUtility.getTextFromPart(textPart);
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
// If we had a body length (and it was valid), separate the composition from the quoted text
// and put them in their respective places in the UI.
if (bodyLength != null && bodyLength + 1 < text.length()) { // + 1 to get rid of the newline we added when saving the draft
String bodyText = text.substring(bodyOffset, bodyOffset + bodyLength);
// Regenerate the quoted text without our user content in it.
StringBuilder quotedText = new StringBuilder();
quotedText.append(text.substring(0, bodyOffset)); // stuff before the reply
quotedText.append(text.substring(bodyOffset + bodyLength));
mMessageContentView.setText(bodyText);
mQuotedText.setText(quotedText.toString());
} else {
mMessageContentView.setText(text);
}
} }
} else if (mMessageFormat == MessageFormat.TEXT) {
processSourceMessageText(message, bodyOffset, bodyLength, true);
} else { } else {
Log.e(K9.LOG_TAG, "Unhandled message format."); Log.e(K9.LOG_TAG, "Unhandled message format.");
} }
@ -2238,6 +2312,51 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} }
} }
/*
* Pull out the parts of the now loaded source message and apply them to the new message
* depending on the type of message being composed.
* @param message Source message
* @param bodyOffset Insertion point for reply.
* @param bodyLength Length of reply.
* @param viewMessageContent Update mMessageContentView or not.
* @throws MessagingException
*/
private void processSourceMessageText(Message message, Integer bodyOffset, Integer bodyLength,
boolean viewMessageContent) throws MessagingException {
Part textPart = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (textPart != null) {
String text = MimeUtility.getTextFromPart(textPart);
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
// If we had a body length (and it was valid), separate the composition from the quoted text
// and put them in their respective places in the UI.
if (bodyLength != null && bodyLength + 1 < text.length()) { // + 1 to get rid of the newline we added when saving the draft
String bodyText = text.substring(bodyOffset, bodyOffset + bodyLength);
// Regenerate the quoted text without our user content in it nor added newlines.
StringBuilder quotedText = new StringBuilder();
if (bodyOffset == 0 && text.substring(bodyLength, bodyLength + 2).equals("\n\n")) {
// top-posting: ignore two newlines at start of quote
quotedText.append(text.substring(bodyLength + 2));
} else if (bodyOffset + bodyLength == text.length() &&
text.substring(bodyOffset - 1, bodyOffset).equals("\n")) {
// bottom-posting: ignore newline at end of quote
quotedText.append(text.substring(0, bodyOffset - 1));
} else {
quotedText.append(text.substring(0, bodyOffset)); // stuff before the reply
quotedText.append(text.substring(bodyOffset + bodyLength));
}
if (viewMessageContent) mMessageContentView.setText(bodyText);
mQuotedText.setText(quotedText.toString());
} else {
if (viewMessageContent) mMessageContentView.setText(text);
}
}
}
/** /**
* Build and populate the UI with the quoted message. * Build and populate the UI with the quoted message.
* @throws MessagingException * @throws MessagingException
@ -2252,12 +2371,14 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
: getBodyTextFromMessage(mSourceMessage, mMessageFormat); : getBodyTextFromMessage(mSourceMessage, mMessageFormat);
if (mMessageFormat == MessageFormat.HTML) { if (mMessageFormat == MessageFormat.HTML) {
// Add the HTML reply header to the top of the content. // Add the HTML reply header to the top of the content.
mQuotedHtmlContent = quoteOriginalHtmlMessage(mSourceMessage, content, mAccount.getQuoteStyle()); mQuotedHtmlContent = quoteOriginalHtmlMessage(mSourceMessage, content, mQuoteStyle);
// Load the message with the reply header. // Load the message with the reply header.
mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null); mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null);
mQuotedText.setText(quoteOriginalTextMessage(mSourceMessage,
getBodyTextFromMessage(mSourceMessage, MessageFormat.TEXT), mQuoteStyle));
} else if (mMessageFormat == MessageFormat.TEXT) { } else if (mMessageFormat == MessageFormat.TEXT) {
mQuotedText.setText(quoteOriginalTextMessage(mSourceMessage, content, mAccount.getQuoteStyle())); mQuotedText.setText(quoteOriginalTextMessage(mSourceMessage, content, mQuoteStyle));
} }
if (shown) { if (shown) {