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.gui.composer;
017:
018: import java.io.File;
019: import java.io.IOException;
020: import java.net.URI;
021: import java.net.URISyntaxException;
022: import java.nio.charset.Charset;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.Vector;
026: import java.util.logging.Logger;
027: import java.util.regex.Pattern;
028:
029: import javax.swing.event.EventListenerList;
030:
031: import org.columba.core.desktop.ColumbaDesktop;
032: import org.columba.mail.command.MailFolderCommandReference;
033: import org.columba.mail.config.AccountItem;
034: import org.columba.mail.config.MailConfig;
035: import org.columba.mail.message.ColumbaMessage;
036: import org.columba.mail.message.IColumbaMessage;
037: import org.columba.mail.parser.ListBuilder;
038: import org.columba.mail.parser.ListParser;
039: import org.columba.mail.parser.NormalizeRecipientListParser;
040: import org.columba.ristretto.io.FileSource;
041: import org.columba.ristretto.message.Address;
042: import org.columba.ristretto.message.Header;
043: import org.columba.ristretto.message.LocalMimePart;
044: import org.columba.ristretto.message.MimeHeader;
045: import org.columba.ristretto.message.StreamableMimePart;
046:
047: /**
048: * @author frd
049: *
050: * Model for message composer dialog
051: *
052: */
053: public class ComposerModel {
054:
055: /** JDK 1.4+ logging framework logger, used for logging. */
056: private static final Logger LOG = Logger
057: .getLogger("org.columba.mail.gui.composer"); //$NON-NLS-1$
058:
059: private ColumbaMessage message;
060:
061: private AccountItem accountItem;
062:
063: private String bodytext;
064:
065: private Charset charset;
066:
067: private List attachments;
068:
069: private List<String> toList;
070:
071: private List<String> ccList;
072:
073: private List<String> bccList;
074:
075: private boolean signMessage;
076:
077: private boolean encryptMessage;
078:
079: /**
080: * this regular expression should cover anything from a@a.pt or a@a.com to
081: * a@a.info. Permits usage of invalid top domains though.
082: * <p>
083: * [bug] fdietz: added "." and "-" as regular characters
084: * (example:mail@toplevel.mail.de)
085: * <p>
086: * TODO: see if we can replace the matching code with Ristretto stuff
087: *
088: */
089: private static final String emailRegExp = "[a-zA-Z0-9]+([_\\.-][a-zA-Z0-9]+)*@([a-zA-Z0-9]+([\\.-][a-zA-Z0-9]+)*)+\\.[a-zA-Z]{2,}";
090:
091: // original: "^[a-zA-Z0-9]+@[a-zA-Z0-9\\.\\-]+\\.[a-zA-Z]{2,4}+$";
092:
093: private static final Pattern emailPattern = Pattern
094: .compile(emailRegExp);
095:
096: /**
097: * source reference
098: * <p>
099: * When replying/forwarding this is the original message you selected in the
100: * message-list and replied to
101: */
102: private MailFolderCommandReference ref;
103:
104: /**
105: * Flag indicating whether this model holds a html message (true) or plain
106: * text (false)
107: */
108: private boolean isHtmlMessage;
109:
110: private EventListenerList listenerList = new EventListenerList();
111:
112: /**
113: * Create a new model with an empty plain text message (default behaviour)
114: */
115: public ComposerModel() {
116: this (null, false); // default ~ plain text
117: }
118:
119: /**
120: * Creates a new model with a plain text message
121: *
122: * @param message
123: * Initial message to hold in the model
124: */
125: public ComposerModel(ColumbaMessage message) {
126: this (message, false);
127: }
128:
129: /**
130: * Creates a new model with an empty message
131: *
132: * @param html
133: * True for a html message, false for plain text
134: */
135: public ComposerModel(boolean html) {
136: this (null, html);
137: }
138:
139: /**
140: * Constructs a new ComposerModel. The parameters are read from the
141: * messageOptions.
142: *
143: * @param messageOptions
144: */
145: public ComposerModel(Map<String, String> messageOptions) {
146: this ();
147: setMessageOptions(messageOptions);
148: }
149:
150: /**
151: * Creates a new model
152: *
153: * @param message
154: * Initial message to hold in the model
155: * @param html
156: * True for a html message, false for plain text
157: */
158: public ComposerModel(ColumbaMessage message, boolean html) {
159: // set message
160: if (message == null) {
161: message = new ColumbaMessage();
162: }
163:
164: this .message = message;
165:
166: // set whether the model should handle html or plain text
167: isHtmlMessage = html;
168:
169: // more initialization
170: toList = new Vector();
171: ccList = new Vector();
172: bccList = new Vector();
173: attachments = new Vector();
174: }
175:
176: /**
177: * Set source reference.
178: * <p>
179: * The message you are for example replying to.
180: *
181: * @param ref
182: * source reference
183: */
184: public void setSourceReference(MailFolderCommandReference ref) {
185: this .ref = ref;
186: }
187:
188: /**
189: * Get source reference.
190: * <p>
191: * The message you are for example replying to.
192: *
193: * @return source reference
194: */
195: public MailFolderCommandReference getSourceReference() {
196: return ref;
197: }
198:
199: /**
200: * Set To: header
201: *
202: * @param a
203: * address array
204: */
205: public void setTo(Address[] a) {
206: getToList().clear();
207:
208: for (int i = 0; i < a.length; i++) {
209: getToList().add(a[i].toString());
210: }
211:
212: }
213:
214: /**
215: * Set Cc: header
216: *
217: * @param a
218: * address array
219: */
220: public void setCc(Address[] a) {
221: getCcList().clear();
222: for (int i = 0; i < a.length; i++) {
223: getCcList().add(a[i].toString());
224: }
225: }
226:
227: /**
228: * Set Bcc: header
229: *
230: * @param a
231: * address array
232: */
233: public void setBcc(Address[] a) {
234: getBccList().clear();
235: for (int i = 0; i < a.length; i++) {
236: getBccList().add(a[i].toString());
237: }
238: }
239:
240: public void setTo(String s) {
241: LOG.fine("to-headerfield:" + s);
242:
243: if (s == null) {
244: return;
245: }
246:
247: if (s.length() == 0) {
248: return;
249: }
250:
251: List v = ListParser.createListFromString(s);
252: toList = v;
253: }
254:
255: public void setHeaderField(String key, String value) {
256: message.getHeader().set(key, value);
257: }
258:
259: public void setHeader(Header header) {
260: message.setHeader(header);
261: }
262:
263: public String getHeaderField(String key) {
264: return (String) message.getHeader().get(key);
265: }
266:
267: public void setToList(List<String> v) {
268: this .toList = v;
269: }
270:
271: public void setCcList(List<String> v) {
272: this .ccList = v;
273: }
274:
275: public void setBccList(List<String> v) {
276: this .bccList = v;
277: }
278:
279: public List<String> getToList() {
280: return toList;
281: }
282:
283: public List<String> getCcList() {
284: return ccList;
285: }
286:
287: public List<String> getBccList() {
288: return bccList;
289: }
290:
291: public void setAccountItem(AccountItem item) {
292: this .accountItem = item;
293: }
294:
295: public AccountItem getAccountItem() {
296: if (accountItem == null) {
297: return MailConfig.getInstance().getAccountList().get(0);
298: } else {
299: return accountItem;
300: }
301: }
302:
303: public void setMessage(ColumbaMessage message) {
304: this .message = message;
305: }
306:
307: public IColumbaMessage getMessage() {
308: return message;
309: }
310:
311: public String getHeader(String key) {
312: return (String) message.getHeader().get(key);
313: }
314:
315: public void addMimePart(StreamableMimePart mp) {
316: attachments.add(mp);
317:
318: // notifyListeners();
319: }
320:
321: public void addFileAttachment(File file) {
322: if (file.isFile()) {
323:
324: String mimetype = ColumbaDesktop.getInstance().getMimeType(
325: file);
326:
327: MimeHeader header = new MimeHeader(mimetype.substring(0,
328: mimetype.indexOf('/')), mimetype.substring(mimetype
329: .indexOf('/') + 1));
330: header.putContentParameter("name", file.getName());
331: header.setContentDisposition("attachment");
332: header.putDispositionParameter("filename", file.getName());
333: header.setContentTransferEncoding("base64");
334:
335: try {
336: LocalMimePart mimePart = new LocalMimePart(header,
337: new FileSource(file));
338:
339: attachments.add(mimePart);
340: } catch (IOException e) {
341: LOG.warning("Could not add the file '" + file
342: + "' to the attachment list, due to:" + e);
343: }
344: }
345:
346: }
347:
348: public void setBodyText(String str) {
349: this .bodytext = str;
350:
351: // notifyListeners();
352: }
353:
354: public String getSignature() {
355: return "signature";
356: }
357:
358: public String getBodyText() {
359: return bodytext;
360: }
361:
362: public String getSubject() {
363: return (String) message.getHeader().get("Subject");
364: }
365:
366: public void setSubject(String s) {
367: message.getHeader().set("Subject", s);
368: }
369:
370: public List getAttachments() {
371: return attachments;
372: }
373:
374: public void setAccountItem(String host, String address) {
375: setAccountItem(MailConfig.getInstance().getAccountList()
376: .hostGetAccount(host, address));
377: }
378:
379: /**
380: * Returns the charsetName.
381: *
382: * @return String
383: */
384: public Charset getCharset() {
385: if (charset == null) {
386: charset = Charset.forName(System
387: .getProperty("file.encoding"));
388: }
389:
390: return charset;
391: }
392:
393: /**
394: * Sets the charsetName.
395: *
396: * @param charsetName
397: * The charsetName to set
398: */
399: public void setCharset(Charset charset) {
400: this .charset = charset;
401: }
402:
403: /**
404: * Returns the signMessage.
405: *
406: * @return boolean
407: */
408: public boolean isSignMessage() {
409: return signMessage;
410: }
411:
412: /**
413: * Sets the signMessage.
414: *
415: * @param signMessage
416: * The signMessage to set
417: */
418: public void setSignMessage(boolean signMessage) {
419: this .signMessage = signMessage;
420: }
421:
422: /**
423: * Returns the encryptMessage.
424: *
425: * @return boolean
426: */
427: public boolean isEncryptMessage() {
428: return encryptMessage;
429: }
430:
431: /**
432: * Sets the encryptMessage.
433: *
434: * @param encryptMessage
435: * The encryptMessage to set
436: */
437: public void setEncryptMessage(boolean encryptMessage) {
438: this .encryptMessage = encryptMessage;
439: }
440:
441: public String getPriority() {
442: if (message.getHeader().get("X-Priority") == null) {
443: return "Normal";
444: } else {
445: return (String) message.getHeader().get("X-Priority");
446: }
447: }
448:
449: public void setPriority(String s) {
450: message.getHeader().set("X-Priority", s);
451: }
452:
453: /**
454: * Returns whether the model holds a html message or plain text
455: *
456: * @return True for html, false for text
457: */
458: public boolean isHtml() {
459: return isHtmlMessage;
460: }
461:
462: /**
463: * Sets whether the model holds a html message or plain text
464: *
465: * @param html
466: * True for html, false for text
467: */
468: public void setHtml(boolean html) {
469: isHtmlMessage = html;
470: }
471:
472: public List getRCPTVector() {
473: List<String> output = new Vector<String>();
474:
475: List<String> l = new NormalizeRecipientListParser()
476: .normalizeRCPTVector(ListBuilder
477: .createFlatList(getToList()));
478: if (l != null)
479: output.addAll(l);
480:
481: l = new NormalizeRecipientListParser()
482: .normalizeRCPTVector(ListBuilder
483: .createFlatList(getCcList()));
484: if (l != null)
485: output.addAll(l);
486: l = new NormalizeRecipientListParser()
487: .normalizeRCPTVector(ListBuilder
488: .createFlatList(getBccList()));
489: if (l != null)
490: output.addAll(l);
491:
492: return output;
493: }
494:
495: public void setMessageOptions(Map<String, String> options) {
496:
497: addAddresses(options, "to");
498: addAddresses(options, "cc");
499: addAddresses(options, "bcc");
500:
501: if (options.get("subject") != null) {
502: setSubject((String) options.get("subject"));
503: }
504:
505: if (options.get("body") != null) {
506: String body = (String) options.get("body");
507: /*
508: * *20030917, karlpeder* Set the model to html or text based on the
509: * body specified on the command line. This is done using a simple
510: * check: Does the body contain <html> and </html>
511: */
512: boolean html = false;
513: String lcase = body.toLowerCase();
514:
515: if ((lcase.indexOf("<html>") != -1)
516: && (lcase.indexOf("</html>") != -1)) {
517: html = true;
518: }
519:
520: setHtml(html);
521:
522: // set the body text
523: setBodyText(body);
524: }
525:
526: if (options.get("attachment") != null) {
527: if (options.get("attachment") instanceof String) {
528: String s = (String) options.get("attachment");
529: try {
530: URI uri = new URI(s);
531: addFileAttachment(new File(uri));
532: } catch (URISyntaxException e) {
533: // if this is no URI
534: addFileAttachment(new File(s));
535: }
536: }
537: }
538:
539: }
540:
541: /**
542: * @param options
543: */
544: private void addAddresses(Map options, String type) {
545: List list;
546:
547: if (type.equals("to")) {
548: list = getToList();
549: } else if (type.equals("cc")) {
550: list = getCcList();
551: } else {
552: list = getBccList();
553: }
554:
555: if (options.get(type) != null) {
556: if (options.get(type) instanceof String) {
557: list.add((String) options.get(type));
558: } else {
559: String[] addresses = (String[]) options.get(type);
560:
561: for (int i = 0; i < addresses.length; i++) {
562: list.add(addresses[i]);
563: }
564: }
565:
566: }
567: }
568:
569: /** **************************** event handling ***************************** */
570:
571: /**
572: * Adds a listener.
573: */
574: public void addModelChangedListener(
575: IComposerModelChangedListener listener) {
576: listenerList.add(IComposerModelChangedListener.class, listener);
577: }
578:
579: /**
580: * Removes a previously registered listener.
581: */
582: public void removeModelChangedListener(
583: IComposerModelChangedListener listener) {
584: listenerList.remove(IComposerModelChangedListener.class,
585: listener);
586: }
587:
588: /**
589: * Propagates an event to all registered listeners notifying them of changes
590: */
591: public void fireModelChanged() {
592: ComposerModelChangedEvent e = new ComposerModelChangedEvent(
593: this );
594: // Guaranteed to return a non-null array
595: Object[] listeners = listenerList.getListenerList();
596:
597: // Process the listeners last to first, notifying
598: // those that are interested in this event
599: for (int i = listeners.length - 2; i >= 0; i -= 2) {
600: if (listeners[i] == IComposerModelChangedListener.class) {
601: ((IComposerModelChangedListener) listeners[i + 1])
602: .modelChanged(e);
603: }
604: }
605: }
606:
607: /**
608: * Propagates an event to all registered listeners notifying them of changes
609: */
610: public void fireHtmlModelChanged(boolean htmlEnabled) {
611: ComposerModelChangedEvent e = new ComposerModelChangedEvent(
612: this , htmlEnabled);
613: // Guaranteed to return a non-null array
614: Object[] listeners = listenerList.getListenerList();
615:
616: // Process the listeners last to first, notifying
617: // those that are interested in this event
618: for (int i = listeners.length - 2; i >= 0; i -= 2) {
619: if (listeners[i] == IComposerModelChangedListener.class) {
620: ((IComposerModelChangedListener) listeners[i + 1])
621: .htmlModeChanged(e);
622: }
623: }
624: }
625: }
|