001: //The contents of this file are subject to the Mozilla Public License Version 1.1
002: //(the "License"); you may not use this file except in compliance with the
003: //License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
004: //
005: //Software distributed under the License is distributed on an "AS IS" basis,
006: //WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
007: //for the specific language governing rights and
008: //limitations under the License.
009: //
010: //The Original Code is "The Columba Project"
011: //
012: //The Initial Developers of the Original Code are Frederik Dietz and Timo Stich.
013: //Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
014: //
015: //All Rights Reserved.
016: package org.columba.mail.folder.command;
017:
018: import java.awt.Color;
019: import java.awt.Font;
020: import java.io.ByteArrayInputStream;
021: import java.io.File;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.lang.reflect.Array;
025: import java.net.MalformedURLException;
026: import java.net.URL;
027: import java.nio.charset.Charset;
028: import java.text.DateFormat;
029: import java.text.ParsePosition;
030: import java.text.SimpleDateFormat;
031: import java.util.Date;
032: import java.util.List;
033: import java.util.logging.Logger;
034:
035: import org.columba.api.command.ICommandReference;
036: import org.columba.api.command.IWorkerStatusController;
037: import org.columba.core.command.Command;
038: import org.columba.core.command.StatusObservableImpl;
039: import org.columba.core.command.Worker;
040: import org.columba.core.config.Config;
041: import org.columba.core.io.DiskIO;
042: import org.columba.core.io.StreamUtils;
043: import org.columba.core.print.cCmUnit;
044: import org.columba.core.print.cDocument;
045: import org.columba.core.print.cHGroup;
046: import org.columba.core.print.cHTMLPart;
047: import org.columba.core.print.cLine;
048: import org.columba.core.print.cParagraph;
049: import org.columba.core.print.cPrintObject;
050: import org.columba.core.print.cPrintVariable;
051: import org.columba.core.print.cVGroup;
052: import org.columba.core.util.TempFileStore;
053: import org.columba.core.xml.XmlElement;
054: import org.columba.mail.command.IMailFolderCommandReference;
055: import org.columba.mail.config.MailConfig;
056: import org.columba.mail.folder.IMailbox;
057: import org.columba.mail.gui.message.viewer.AttachmentModel;
058: import org.columba.mail.parser.text.HtmlParser;
059: import org.columba.mail.util.MailResourceLoader;
060: import org.columba.ristretto.coder.Base64DecoderInputStream;
061: import org.columba.ristretto.coder.CharsetDecoderInputStream;
062: import org.columba.ristretto.coder.QuotedPrintableDecoderInputStream;
063: import org.columba.ristretto.message.Header;
064: import org.columba.ristretto.message.MimeHeader;
065: import org.columba.ristretto.message.MimePart;
066: import org.columba.ristretto.message.MimeTree;
067: import org.columba.ristretto.message.StreamableMimePart;
068:
069: /**
070: * Print the selected message.
071: *
072: * @author karlpeder
073: */
074: public class PrintMessageCommand extends Command {
075:
076: /** JDK 1.4+ logging framework logger, used for logging. */
077: private static final Logger LOG = Logger
078: .getLogger("org.columba.mail.folder.command");
079:
080: private cPrintObject mailHeader;
081: private cPrintObject mailFooter;
082: private DateFormat mailDateFormat;
083: private String[] headerKeys = { "From", "To", "Date", "Subject" };
084: private String dateHeaderKey = "Date"; // the header key for date field
085: private String attHeaderKey = "attachment";
086: private Charset charset;
087:
088: private MimeHeader bodyHeader;
089: private InputStream bodyStream;
090:
091: /**
092: * Constructor for PrintMessageCommdn.
093: *
094: * @param frameMediator
095: * @param references
096: */
097: public PrintMessageCommand(ICommandReference reference,
098: Charset charset) {
099: super (reference);
100: this .charset = charset;
101:
102: // Header
103: cParagraph columbaParagraph = new cParagraph();
104: columbaParagraph.setText("The Columba Project");
105: columbaParagraph.setColor(Color.lightGray);
106: columbaParagraph.setFontStyle(Font.BOLD);
107:
108: cParagraph link = new cParagraph();
109: link.setText(" - http://www.columbamail.org");
110: link.setTextAlignment(cParagraph.LEFT);
111: link.setLeftMargin(columbaParagraph.getSize(new cCmUnit(100))
112: .getWidth());
113: link.setColor(Color.lightGray);
114:
115: cPrintVariable date = new cPrintVariable();
116: date.setCodeString("%DATE_TODAY%");
117: date.setTextAlignment(cParagraph.RIGHT);
118: date.setColor(Color.lightGray);
119:
120: cHGroup headerText = new cHGroup();
121: headerText.add(columbaParagraph);
122: headerText.add(link);
123: headerText.add(date);
124:
125: cLine headerLine = new cLine();
126:
127: headerLine.setThickness(1);
128: headerLine.setColor(Color.lightGray);
129: headerLine.setTopMargin(new cCmUnit(0.1));
130:
131: cVGroup header = new cVGroup();
132: header.add(headerText);
133: header.add(headerLine);
134: header.setBottomMargin(new cCmUnit(0.5));
135:
136: mailHeader = header;
137:
138: // Footer
139: cPrintVariable footer = new cPrintVariable();
140: footer.setTextAlignment(cParagraph.CENTER);
141: footer.setCodeString("%PAGE_NR% / %PAGE_COUNT%");
142: footer.setTopMargin(new cCmUnit(0.5));
143: footer.setColor(Color.lightGray);
144:
145: mailFooter = footer;
146:
147: // DateFormat
148: mailDateFormat = DateFormat.getDateTimeInstance(
149: DateFormat.LONG, DateFormat.MEDIUM);
150: }
151:
152: public cPrintObject getMailHeader() {
153: return mailHeader;
154: }
155:
156: public cPrintObject getMailFooter() {
157: return mailFooter;
158: }
159:
160: public String[] getHeaderKeys() {
161: return headerKeys;
162: }
163:
164: public DateFormat getMailDateFormat() {
165: return mailDateFormat;
166: }
167:
168: /**
169: * @see org.columba.api.command.Command#updateGUI()
170: */
171: public void updatedGUI() throws Exception {
172: }
173:
174: /**
175: * This method executes the print action, i.e. it prints the selected
176: * messages.
177: *
178: * @see org.columba.api.command.Command#execute(Worker)
179: */
180: public void execute(IWorkerStatusController worker)
181: throws Exception {
182: /*
183: * *20030604, karlpeder* Fixed minor flaws to be able to print text
184: * messages. Further more added support for html messages.
185: */
186: IMailFolderCommandReference r = (IMailFolderCommandReference) getReference();
187:
188: Object[] uids = r.getUids(); // uid for messages to print
189:
190: IMailbox srcFolder = (IMailbox) r.getSourceFolder();
191:
192: //register for status events
193: ((StatusObservableImpl) srcFolder.getObservable())
194: .setWorker(worker);
195:
196: // Print each message
197: for (int j = 0; j < uids.length; j++) {
198: Object uid = uids[j];
199: LOG.info("Printing UID=" + uid);
200:
201: Header header = srcFolder.getHeaderFields(uids[j],
202: getHeaderKeys());
203:
204: setupMessageBodyPart(uid, srcFolder, worker);
205:
206: // Setup print document for message
207: cDocument messageDoc = new cDocument();
208: messageDoc.setHeader(getMailHeader());
209: messageDoc.setFooter(getMailFooter());
210:
211: String[] headerKeys = getHeaderKeys();
212: cParagraph hKey;
213: cParagraph hValue;
214: cHGroup hLine;
215: Object value;
216:
217: // Add header information to print
218: for (int i = 0; i < Array.getLength(headerKeys); i++) {
219: hKey = new cParagraph();
220:
221: // *20030531, karlpeder* setting headerKeys to lowercase for
222: // lookup!
223: hKey.setText(MailResourceLoader.getString("header",
224: headerKeys[i].toLowerCase()));
225: hKey.setFontStyle(Font.BOLD);
226:
227: hValue = new cParagraph();
228:
229: /*
230: * *20031216, karlpeder* Changed handling of dates.
231: * Previously columba.date header was used. Now we
232: * use the Date header instead
233: */
234: //if (headerKeys[i].equalsIgnoreCase("date")) {
235: // value = header.get("columba.date");
236: //} else {
237: //value = header.get(headerKeys[i]);
238: //}
239: value = header.get(headerKeys[i]);
240:
241: if (headerKeys[i].equalsIgnoreCase(dateHeaderKey)) {
242: // special handling for dates
243: SimpleDateFormat formatter = new SimpleDateFormat(
244: "d MMM yyyy HH:mm:ss Z");
245: String dateStr = (String) value;
246:
247: // ignore leading weekday name (e.g. "Mon,"), since this
248: // seems to give problems during parsing
249: ParsePosition pos = new ParsePosition(dateStr
250: .indexOf(',') + 1);
251: Date d = formatter.parse((String) value, pos);
252:
253: if (d != null) {
254: hValue.setText(getMailDateFormat().format(d));
255: } else {
256: // fall back to use the Date header contents directly
257: hValue.setText((String) value);
258: }
259: } else {
260: hValue.setText((String) value);
261: }
262:
263: hValue.setLeftMargin(new cCmUnit(3.0));
264:
265: hLine = new cHGroup();
266: hLine.add(hKey);
267: hLine.add(hValue);
268:
269: messageDoc.appendPrintObject(hLine);
270: }
271:
272: // Add list of attachments if applicable
273: AttachmentModel attMod = new AttachmentModel();
274: attMod.setCollection(srcFolder.getMimePartTree(uid));
275:
276: List attachments = attMod.getDisplayedMimeParts();
277:
278: for (int i = 0; i < attachments.size(); i++) {
279: StreamableMimePart mp = (StreamableMimePart) attachments
280: .get(i);
281: if (mp.getHeader().getFileName() != null) {
282: // one line is added to the header for each attachment
283: // (which has a filename defined)
284: hKey = new cParagraph();
285: hKey.setText(MailResourceLoader.getString("header",
286: attHeaderKey));
287: hKey.setFontStyle(Font.BOLD);
288:
289: hValue = new cParagraph();
290: hValue.setText(mp.getHeader().getFileName());
291: hValue.setLeftMargin(new cCmUnit(3.0));
292:
293: hLine = new cHGroup();
294: hLine.add(hKey);
295: hLine.add(hValue);
296:
297: messageDoc.appendPrintObject(hLine);
298: }
299: }
300:
301: // Add body of message to print
302: String mimesubtype = bodyHeader.getMimeType().getSubtype();
303:
304: if (mimesubtype.equals("html")) {
305: messageDoc.appendPrintObject(getHTMLBodyPrintObject());
306: } else {
307: messageDoc.appendPrintObject(getPlainBodyPrintObject());
308: }
309:
310: // print the print document (i.e. the message)
311: messageDoc.print();
312: }
313:
314: // end of for loop over uids to print
315: }
316:
317: /**
318: * Private utility to create a print object representing the body of a
319: * plain text message. The messagebody is decoded according to present
320: * charset. <br>Precondition: Mime subtype is "plain".
321: *
322: * @param bodyPart
323: * Body part of message
324: * @return Print object ready to be appended to the print document
325: * @author Karl Peder Olesen (karlpeder), 20030531
326: */
327: private cPrintObject getPlainBodyPrintObject() throws IOException {
328: // decode message body with respect to charset
329: String decodedBody = getDecodedMessageBody();
330:
331: // create a print object and return it
332: cParagraph printBody = new cParagraph();
333: printBody.setTopMargin(new cCmUnit(1.0));
334: printBody.setText(decodedBody);
335:
336: return printBody;
337: }
338:
339: /**
340: * retrieve printer options from configuration file
341: *
342: * @return true, if scaling is allowed false, otherwise
343: */
344: protected boolean isScalingAllowed() {
345: XmlElement options = Config.getInstance().get("options")
346: .getElement("/options");
347: XmlElement printer = null;
348:
349: if (options != null) {
350: printer = options.getElement("/printer");
351: }
352:
353: // no configuration available, create default config
354: if (printer == null) {
355: // create new local xml treenode
356: LOG.info("printer config node not found - creating new");
357: printer = new XmlElement("printer");
358: printer.addAttribute("allow_scaling", "true");
359:
360: // add to options if possible (so it will be saved)
361: if (options != null) {
362: LOG.info("storing new printer config node");
363: options.addElement(printer);
364: }
365: }
366:
367: return Boolean.valueOf(
368: printer.getAttribute("allow_scaling", "true"))
369: .booleanValue();
370: }
371:
372: /**
373: * Private utility to create a print object representing the body of a html
374: * message. <br>Precondition: Mime subtype is "html".
375: *
376: * @param bodyPart
377: * Body part of message
378: * @return Print object ready to be appended to the print document
379: * @author Karl Peder Olesen (karlpeder), 20030531
380: */
381: private cPrintObject getHTMLBodyPrintObject() throws IOException {
382: // decode message body with respect to charset
383: String decodedBody = getDecodedMessageBody();
384:
385: // try to fix broken html-strings
386: String validated = HtmlParser.validateHTMLString(decodedBody);
387:
388: try {
389: // create temporary file and save validated body
390: File tempFile = TempFileStore
391: .createTempFileWithSuffix("html");
392: DiskIO.saveStringInFile(tempFile, validated);
393:
394: URL url = tempFile.toURL();
395:
396: boolean allowScaling = isScalingAllowed();
397: cHTMLPart htmlBody = new cHTMLPart(allowScaling);
398:
399: // true ~ scaling allowed
400: htmlBody.setTopMargin(new cCmUnit(1.0));
401: htmlBody.setHTML(url);
402:
403: return htmlBody;
404: } catch (MalformedURLException e) {
405: LOG.warning("Error loading html for print: "
406: + e.getMessage());
407:
408: return null;
409: } catch (IOException e) {
410: LOG.warning("Error loading html for print: "
411: + e.getMessage());
412:
413: return null;
414: }
415: }
416:
417: /**
418: * Private utility to decode the message body with the proper charset
419: *
420: * @param bodyPart
421: * The body of the message
422: * @return Decoded message body
423: * @author Karl Peder Olesen (karlpeder), 20030601
424: */
425: private String getDecodedMessageBody() throws IOException {
426: int encoding = bodyHeader.getContentTransferEncoding();
427:
428: switch (encoding) {
429: case MimeHeader.QUOTED_PRINTABLE: {
430: bodyStream = new QuotedPrintableDecoderInputStream(
431: bodyStream);
432:
433: break;
434: }
435:
436: case MimeHeader.BASE64: {
437: bodyStream = new Base64DecoderInputStream(bodyStream);
438:
439: break;
440: }
441: }
442:
443: // First determine which charset to use
444: if (charset == null) {
445: try {
446: // get charset from message
447: charset = Charset.forName(bodyHeader
448: .getContentParameter("charset"));
449: } catch (Exception ex) {
450: // decode using default charset
451: charset = Charset.forName(System
452: .getProperty("file.encoding"));
453: }
454: }
455:
456: bodyStream = new CharsetDecoderInputStream(bodyStream, charset);
457:
458: return StreamUtils.readCharacterStream(bodyStream).toString();
459: }
460:
461: /**
462: * Private utility to get body part of a message. User preferences
463: * regarding html messages is used to select what to retrieve. If the body
464: * part retrieved is null, a fake one containing a simple text is returned
465: *
466: * @param uid
467: * ID of message
468: * @param srcFolder
469: * AbstractMessageFolder containing the message
470: * @param worker
471: * @return body part of message
472: */
473: private void setupMessageBodyPart(Object uid, IMailbox srcFolder,
474: IWorkerStatusController worker) throws Exception {
475: // Does the user prefer html or plain text?
476: XmlElement html = MailConfig.getInstance()
477: .getMainFrameOptionsConfig().getRoot().getElement(
478: "/options/html");
479:
480: // Get body of message depending on user preferences
481: MimeTree mimePartTree = srcFolder.getMimePartTree(uid);
482:
483: MimePart bodyPart = null;
484:
485: if (Boolean.valueOf(html.getAttribute("prefer")).booleanValue()) {
486: bodyPart = mimePartTree.getFirstTextPart("html");
487: } else {
488: bodyPart = mimePartTree.getFirstTextPart("plain");
489: }
490:
491: if (bodyPart == null) {
492: bodyHeader = new MimeHeader();
493: bodyStream = new ByteArrayInputStream(new byte[0]);
494: } else {
495: bodyHeader = bodyPart.getHeader();
496: bodyStream = srcFolder.getMimePartBodyStream(uid, bodyPart
497: .getAddress());
498: }
499: }
500:
501: }
|