0001: /*
0002: * Mailbox.java
0003: *
0004: * Copyright (C) 2000-2003 Peter Graves
0005: * $Id: Mailbox.java,v 1.6 2003/08/09 17:39:07 piso Exp $
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License
0009: * as published by the Free Software Foundation; either version 2
0010: * of the License, or (at your option) any later version.
0011: *
0012: * This program is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015: * GNU General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * along with this program; if not, write to the Free Software
0019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0020: */
0021:
0022: package org.armedbear.j.mail;
0023:
0024: import java.util.ArrayList;
0025: import java.util.Collections;
0026: import java.util.Comparator;
0027: import java.util.Iterator;
0028: import java.util.List;
0029: import javax.swing.Icon;
0030: import javax.swing.SwingUtilities;
0031: import org.armedbear.j.Buffer;
0032: import org.armedbear.j.Debug;
0033: import org.armedbear.j.Dispatcher;
0034: import org.armedbear.j.Display;
0035: import org.armedbear.j.Editor;
0036: import org.armedbear.j.EditorIterator;
0037: import org.armedbear.j.FastStringBuffer;
0038: import org.armedbear.j.History;
0039: import org.armedbear.j.InputDialog;
0040: import org.armedbear.j.Line;
0041: import org.armedbear.j.Log;
0042: import org.armedbear.j.MessageDialog;
0043: import org.armedbear.j.Position;
0044: import org.armedbear.j.ProgressNotifier;
0045: import org.armedbear.j.Property;
0046: import org.armedbear.j.PropertyList;
0047: import org.armedbear.j.Sidebar;
0048: import org.armedbear.j.Utilities;
0049: import org.armedbear.j.View;
0050:
0051: public abstract class Mailbox extends Buffer {
0052: public static final int SORT_BY_DATE_SENT = 0;
0053:
0054: protected MailboxURL url;
0055:
0056: boolean showFullHeaders;
0057: boolean showRawText;
0058:
0059: protected boolean dirty;
0060:
0061: protected int unreadMessageCount;
0062: protected int newMessageCount;
0063:
0064: protected List entries;
0065:
0066: private long lastCheckMillis;
0067:
0068: private int sortBy = SORT_BY_DATE_SENT;
0069:
0070: private MessageBuffer previewBuffer;
0071:
0072: protected Mailbox() {
0073: }
0074:
0075: protected Mailbox(MailboxURL url) {
0076: this .url = url;
0077: PropertyList props = MailboxProperties.getProperties(url);
0078: if (props != null)
0079: properties.putAll(props);
0080: }
0081:
0082: public MessageBuffer getPreviewBuffer() {
0083: return previewBuffer;
0084: }
0085:
0086: public void setPreviewBuffer(MessageBuffer buf) {
0087: previewBuffer = buf;
0088: }
0089:
0090: public Buffer getSecondary() {
0091: return previewBuffer;
0092: }
0093:
0094: public final MailboxURL getUrl() {
0095: return url;
0096: }
0097:
0098: public final int getSortBy() {
0099: return sortBy;
0100: }
0101:
0102: public abstract String getName();
0103:
0104: public synchronized final void setEntries(List entries) {
0105: this .entries = entries;
0106: }
0107:
0108: public synchronized final long getLastCheckMillis() {
0109: return lastCheckMillis;
0110: }
0111:
0112: protected synchronized final void setLastCheckMillis(long when) {
0113: lastCheckMillis = when;
0114: }
0115:
0116: public synchronized long getLastErrorMillis() {
0117: return 0;
0118: }
0119:
0120: public abstract void getNewMessages();
0121:
0122: public void getNewMessages(boolean userInitiated) {
0123: }
0124:
0125: public abstract void readMessage(Line line);
0126:
0127: public void readMessageOtherWindow(Line line) {
0128: }
0129:
0130: protected void activateMessageBuffer(Editor editor,
0131: MessageBuffer messageBuffer, boolean useOtherWindow) {
0132: editor.makeNext(messageBuffer);
0133: if (useOtherWindow) {
0134: Buffer oldBuffer = null;
0135: Editor ed = editor.getOtherEditor();
0136: if (ed != null)
0137: oldBuffer = ed.getBuffer();
0138: messageBuffer.setTransient(true);
0139: editor.activateInOtherWindow(messageBuffer, messageBuffer
0140: .getSplit());
0141: previewBuffer = messageBuffer;
0142: if (oldBuffer != null && oldBuffer != messageBuffer) {
0143: if (oldBuffer.isTransient())
0144: oldBuffer.kill();
0145: }
0146: } else
0147: editor.activate(messageBuffer);
0148: }
0149:
0150: public abstract void createFolder();
0151:
0152: public abstract void deleteFolder();
0153:
0154: public abstract void saveToFolder();
0155:
0156: public abstract void moveToFolder();
0157:
0158: public abstract void delete();
0159:
0160: public abstract void undelete();
0161:
0162: public abstract void markRead();
0163:
0164: public abstract void markUnread();
0165:
0166: public void flag() {
0167: }
0168:
0169: public abstract void setAnsweredFlag(MailboxEntry entry);
0170:
0171: public final int getUnreadMessageCount() {
0172: return unreadMessageCount;
0173: }
0174:
0175: public final int getNewMessageCount() {
0176: return newMessageCount;
0177: }
0178:
0179: // Count new and unread messages, respecting the limit that is in force
0180: // (if any).
0181: public void countMessages() {
0182: unreadMessageCount = newMessageCount = 0;
0183: for (Line line = getFirstLine(); line != null; line = line
0184: .next()) {
0185: if (line instanceof MailboxLine) {
0186: MailboxEntry entry = ((MailboxLine) line)
0187: .getMailboxEntry();
0188: if (entry.isNew())
0189: ++newMessageCount;
0190: if (entry.isUnread())
0191: ++unreadMessageCount;
0192: }
0193: }
0194: }
0195:
0196: // Clear RECENT flag for all messages.
0197: // Returns true if there was any change.
0198: protected boolean clearRecent() {
0199: if (!isLocked())
0200: Debug.bug("clearRecent mailbox not locked!");
0201: boolean changed = false;
0202: if (entries != null) {
0203: final int size = entries.size();
0204: for (int i = 0; i < size; i++) {
0205: MailboxEntry entry = (MailboxEntry) entries.get(i);
0206: if ((entry.getFlags() & MailboxEntry.RECENT) != 0) {
0207: entry.setFlags(entry.getFlags()
0208: & ~MailboxEntry.RECENT);
0209: changed = true;
0210: }
0211: }
0212: }
0213: if (changed)
0214: setDirty(true);
0215: return changed;
0216: }
0217:
0218: public final void setDirty(boolean b) {
0219: dirty = b;
0220: }
0221:
0222: public final boolean isDirty() {
0223: return dirty;
0224: }
0225:
0226: public void tag(Editor editor, Line line) {
0227: if (line instanceof MailboxLine) {
0228: MailboxEntry entry = ((MailboxLine) line).getMailboxEntry();
0229: entry.toggleTag();
0230: Editor.updateInAllEditors(line);
0231: if (line.next() != null) {
0232: editor.getDot().setLine(line.next());
0233: editor.setMark(null);
0234: editor.moveDotToCaretCol();
0235: }
0236: }
0237: }
0238:
0239: public void tagPattern() {
0240: Editor editor = Editor.currentEditor();
0241: InputDialog d = new InputDialog(editor, "Pattern:",
0242: "Tag Pattern", null);
0243: d.setHistory(new History("mailboxTagPattern"));
0244: editor.centerDialog(d);
0245: d.show();
0246: String pattern = d.getInput();
0247: if (pattern == null)
0248: return;
0249: pattern = pattern.trim();
0250: if (pattern.length() == 0)
0251: return;
0252: editor.repaintNow();
0253: MailboxFilter filter = MailboxFilter.getMailboxFilter(pattern);
0254: if (filter != null)
0255: tag(filter);
0256: else
0257: MessageDialog.showMessageDialog("Bad pattern",
0258: "Tag Pattern");
0259: }
0260:
0261: private void tag(MailboxFilter filter) {
0262: Editor.currentEditor().setWaitCursor();
0263: for (Line line = getFirstLine(); line != null; line = line
0264: .next()) {
0265: if (line instanceof MailboxLine) {
0266: MailboxEntry entry = ((MailboxLine) line)
0267: .getMailboxEntry();
0268: if (!entry.isTagged()) {
0269: if (filter.accept(entry)) {
0270: entry.tag();
0271: Editor.updateInAllEditors(line);
0272: }
0273: }
0274: }
0275: }
0276: }
0277:
0278: public void untagAll() {
0279: Editor.currentEditor().setWaitCursor();
0280: for (Line line = getFirstLine(); line != null; line = line
0281: .next()) {
0282: if (line instanceof MailboxLine) {
0283: MailboxEntry entry = ((MailboxLine) line)
0284: .getMailboxEntry();
0285: if (entry.isTagged()) {
0286: entry.untag();
0287: Editor.updateInAllEditors(line);
0288: }
0289: }
0290: }
0291: }
0292:
0293: public void toggleRaw() {
0294: showRawText = !showRawText;
0295: Editor.currentEditor().status(
0296: "Raw mode " + (showRawText ? "on" : "off"));
0297: }
0298:
0299: public List getEntries() {
0300: return entries;
0301: }
0302:
0303: public List getTaggedEntries() {
0304: ArrayList taggedEntries = null;
0305: final int size = entries.size();
0306: for (int i = 0; i < size; i++) {
0307: MailboxEntry entry = (MailboxEntry) entries.get(i);
0308: if (entry.isTagged()) {
0309: if (taggedEntries == null)
0310: taggedEntries = new ArrayList();
0311: taggedEntries.add(entry);
0312: }
0313: }
0314: return taggedEntries;
0315: }
0316:
0317: public MailboxEntry getEntryAtDot(Editor editor) {
0318: Line line = editor.getDotLine();
0319: if (line instanceof MailboxLine)
0320: return ((MailboxLine) line).getMailboxEntry();
0321: else
0322: return null;
0323: }
0324:
0325: public abstract void expunge();
0326:
0327: public abstract int getMessageCount();
0328:
0329: public Message getMessage(MailboxEntry entry,
0330: ProgressNotifier progressNotifier) {
0331: Debug.assertTrue(false);
0332: return null;
0333: }
0334:
0335: public MailboxEntry getNextUndeleted(MailboxEntry entry) {
0336: Line line;
0337: for (line = getFirstLine(); line != null; line = line.next()) {
0338: if (line instanceof MailboxLine) {
0339: if (entry == ((MailboxLine) line).getMailboxEntry())
0340: break;
0341: }
0342: }
0343: if (line != null) {
0344: for (line = line.next(); line != null; line = line.next()) {
0345: if (line instanceof MailboxLine) {
0346: MailboxEntry maybe = ((MailboxLine) line)
0347: .getMailboxEntry();
0348: if (!maybe.isDeleted())
0349: return maybe;
0350: }
0351: }
0352: }
0353: return null;
0354: }
0355:
0356: public MailboxEntry getPreviousUndeleted(MailboxEntry entry) {
0357: Line line;
0358: for (line = getFirstLine(); line != null; line = line.next()) {
0359: if (line instanceof MailboxLine) {
0360: if (entry == ((MailboxLine) line).getMailboxEntry())
0361: break;
0362: }
0363: }
0364: if (line != null) {
0365: for (line = line.previous(); line != null; line = line
0366: .previous()) {
0367: if (line instanceof MailboxLine) {
0368: MailboxEntry maybe = ((MailboxLine) line)
0369: .getMailboxEntry();
0370: if (!maybe.isDeleted())
0371: return maybe;
0372: }
0373: }
0374: }
0375: return null;
0376: }
0377:
0378: public MailboxEntry getNextInThread(MailboxEntry entry) {
0379: String subject = entry.getSubject();
0380: if (subject == null)
0381: return null; // But there are other things we could try...
0382: if (subject.toLowerCase().startsWith("re: "))
0383: subject = subject.substring(4);
0384: // Find current entry.
0385: Line line;
0386: for (line = getFirstLine(); line != null; line = line.next()) {
0387: if (line instanceof MailboxLine)
0388: if (entry == ((MailboxLine) line).getMailboxEntry())
0389: break;
0390: }
0391: if (line != null) {
0392: // Search later entries.
0393: for (line = line.next(); line != null; line = line.next()) {
0394: if (line instanceof MailboxLine) {
0395: MailboxEntry maybe = ((MailboxLine) line)
0396: .getMailboxEntry();
0397: // Note that we don't skip over deleted messages here.
0398: String s = maybe.getSubject();
0399: if (s != null) {
0400: if (s.toLowerCase().startsWith("re: "))
0401: s = s.substring(4);
0402: if (s.equals(subject))
0403: return maybe;
0404: }
0405: }
0406: }
0407: }
0408: return null;
0409: }
0410:
0411: public MailboxEntry getPreviousInThread(MailboxEntry entry) {
0412: String subject = entry.getSubject();
0413: if (subject == null)
0414: return null; // But there are other things we could try...
0415: if (subject.toLowerCase().startsWith("re: "))
0416: subject = subject.substring(4);
0417: // Find current entry.
0418: Line line;
0419: for (line = getFirstLine(); line != null; line = line.next()) {
0420: if (line instanceof MailboxLine)
0421: if (entry == ((MailboxLine) line).getMailboxEntry())
0422: break;
0423: }
0424: if (line != null) {
0425: // Search earlier entries.
0426: for (line = line.previous(); line != null; line = line
0427: .previous()) {
0428: if (line instanceof MailboxLine) {
0429: MailboxEntry maybe = ((MailboxLine) line)
0430: .getMailboxEntry();
0431: // Note that we don't skip over deleted messages here.
0432: String s = maybe.getSubject();
0433: if (s != null) {
0434: if (s.toLowerCase().startsWith("re: "))
0435: s = s.substring(4);
0436: if (s.equals(subject))
0437: return maybe;
0438: }
0439: }
0440: }
0441: }
0442: return null;
0443: }
0444:
0445: public MailboxEntry getEntryForMessageId(String messageId) {
0446: if (messageId == null)
0447: return null;
0448: if (entries != null) {
0449: for (int i = entries.size() - 1; i >= 0; i--) {
0450: MailboxEntry entry = (MailboxEntry) entries.get(i);
0451: if (entry != null) {
0452: if (messageId.equals(entry.getMessageId()))
0453: return entry;
0454: }
0455: }
0456: }
0457: return null;
0458: }
0459:
0460: public void bounce() {
0461: Debug.assertTrue(SwingUtilities.isEventDispatchThread());
0462: final Editor editor = Editor.currentEditor();
0463: List list = getTaggedEntries();
0464: if (list == null) {
0465: MailboxEntry entry = getEntryAtDot(editor);
0466: if (entry == null)
0467: return;
0468: list = new ArrayList(1);
0469: list.add(entry);
0470: }
0471: final List toBeBounced = list;
0472: // Get bounce addresses from user.
0473: final MailAddress[] to = MailCommands.bounceGetTo(editor,
0474: toBeBounced.size());
0475: if (to == null)
0476: return;
0477: Runnable bounceRunnable = new Runnable() {
0478: public void run() {
0479: boolean succeeded = false;
0480: try {
0481: succeeded = bounceMessages(toBeBounced, to);
0482: } finally {
0483: unlock();
0484: setBusy(false);
0485: editor.updateDisplayLater();
0486: }
0487: if (succeeded) {
0488: Runnable successRunnable = new Runnable() {
0489: public void run() {
0490: final int size = toBeBounced.size();
0491: FastStringBuffer sb = new FastStringBuffer(
0492: String.valueOf(size));
0493: sb.append(" message");
0494: if (size > 1)
0495: sb.append('s');
0496: sb.append(" bounced");
0497: editor.status(sb.toString());
0498: }
0499: };
0500: SwingUtilities.invokeLater(successRunnable);
0501: } else {
0502: Runnable errorRunnable = new Runnable() {
0503: public void run() {
0504: MessageDialog.showMessageDialog(editor,
0505: "Failed", "Bounce");
0506: }
0507: };
0508: SwingUtilities.invokeLater(errorRunnable);
0509: }
0510: }
0511: };
0512: if (lock()) {
0513: setBusy(true);
0514: new Thread(bounceRunnable).start();
0515: } else
0516: editor.status("Mailbox is locked");
0517: }
0518:
0519: private boolean bounceMessages(List toBeBounced, MailAddress[] to) {
0520: Log.debug("bounceMessages initializing SMTP session...");
0521: SmtpSession smtp = SmtpSession.getDefaultSession();
0522: if (smtp == null)
0523: return false;
0524: try {
0525: for (int i = 0; i < toBeBounced.size(); i++) {
0526: MailboxEntry entry = (MailboxEntry) toBeBounced.get(i);
0527: if (!Mail.bounceMessage(getMessage(entry, null), to,
0528: smtp))
0529: return false;
0530: }
0531: return true;
0532: } finally {
0533: Debug.assertTrue(smtp != null);
0534: Log.debug("bounceMessages closing SMTP session...");
0535: smtp.quit();
0536: }
0537: }
0538:
0539: private String limitPattern;
0540:
0541: public final String getLimitPattern() {
0542: return limitPattern;
0543: }
0544:
0545: public final void setLimitPattern(String pattern) {
0546: limitPattern = pattern;
0547: }
0548:
0549: public void limit() {
0550: Editor editor = Editor.currentEditor();
0551: InputDialog d = new InputDialog(editor, "Pattern:", "Limit",
0552: limitPattern);
0553: d.setHistory(new History("mailboxLimit"));
0554: editor.centerDialog(d);
0555: d.show();
0556: String pattern = d.getInput();
0557: if (pattern == null)
0558: return; // No change (user cancelled input dialog).
0559: editor.repaintNow();
0560: pattern = pattern.trim();
0561: if (pattern.length() == 0)
0562: unlimit();
0563: else {
0564: MailboxFilter filter = MailboxFilter
0565: .getMailboxFilter(pattern);
0566: if (filter != null) {
0567: if (lock()) {
0568: try {
0569: limitPattern = pattern;
0570: limit(filter);
0571: } finally {
0572: unlock();
0573: }
0574: } else
0575: editor.status("Mailbox is locked");
0576: } else
0577: MessageDialog.showMessageDialog("Bad limit pattern",
0578: "Limit");
0579: }
0580: }
0581:
0582: public void unlimit() {
0583: if (lock()) {
0584: try {
0585: limitPattern = null;
0586: limit(null);
0587: } finally {
0588: unlock();
0589: }
0590: } else
0591: Editor.currentEditor().status("Mailbox is locked");
0592: }
0593:
0594: // The limit filter that's currently in effect.
0595: private MailboxFilter limitFilter;
0596:
0597: public final MailboxFilter getLimitFilter() {
0598: return limitFilter;
0599: }
0600:
0601: public final void setLimitFilter(MailboxFilter filter) {
0602: limitFilter = filter;
0603: }
0604:
0605: public void limit(MailboxFilter filter) {
0606: Debug.assertTrue(SwingUtilities.isEventDispatchThread());
0607: final Editor editor = Editor.currentEditor();
0608: editor.repaintNow();
0609: editor.setWaitCursor();
0610: // Remember where we are.
0611: MailboxEntry currentEntry = null;
0612: if (editor.getDot() != null
0613: && editor.getDotLine() instanceof MailboxLine)
0614: currentEntry = ((MailboxLine) editor.getDotLine())
0615: .getMailboxEntry();
0616: limitFilter = filter;
0617: refreshBuffer();
0618: // Update message count in sidebar buffer list.
0619: Sidebar.repaintBufferListInAllFrames();
0620: Line dotLine = null;
0621: if (getFirstLine() != null) {
0622: dotLine = getFirstLine();
0623: if (currentEntry != null) {
0624: boolean groupByThread = getBooleanProperty(Property.GROUP_BY_THREAD);
0625: for (Line line = getFirstLine(); line != null; line = line
0626: .next()) {
0627: MailboxEntry entry = ((MailboxLine) line)
0628: .getMailboxEntry();
0629: if (entry == currentEntry) {
0630: dotLine = line;
0631: break;
0632: }
0633: if (!groupByThread) {
0634: if (RFC822Date.compare(entry.getDate(),
0635: currentEntry.getDate()) < 0)
0636: dotLine = line;
0637: else
0638: break;
0639: }
0640: }
0641: }
0642: }
0643: invalidate();
0644: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
0645: Editor ed = it.nextEditor();
0646: if (ed.getBuffer() == this ) {
0647: ed.updateLocation();
0648: Display display = ed.getDisplay();
0649: if (dotLine != null) {
0650: ed.setDot(dotLine, 0);
0651: display.moveCaretToDotCol();
0652: ed.setMark(null);
0653: display.setTopLine(getFirstLine());
0654: display.setUpdateFlag(REPAINT);
0655: } else {
0656: ed.setDot(null);
0657: ed.setMark(null);
0658: display.setTopLine(null);
0659: }
0660: ed.updateDisplay();
0661: }
0662: }
0663: editor.setDefaultCursor();
0664: }
0665:
0666: public MailboxLine getLineForEntry(MailboxEntry entry) {
0667: if (entry != null) {
0668: for (Line line = getFirstLine(); line != null; line = line
0669: .next()) {
0670: if (line instanceof MailboxLine)
0671: if (((MailboxLine) line).getMailboxEntry() == entry)
0672: return (MailboxLine) line;
0673: }
0674: }
0675: return null;
0676: }
0677:
0678: public MailboxLine findLineForEntry(MailboxEntry entry) {
0679: if (entry != null) {
0680: // First look through all the entries for an exact match.
0681: for (Line l = getFirstLine(); l != null; l = l.next()) {
0682: MailboxLine line = (MailboxLine) l;
0683: MailboxEntry e = line.getMailboxEntry();
0684: if (e == entry)
0685: return line;
0686: }
0687: // We didn't find an exact match.
0688: boolean groupByThread = getBooleanProperty(Property.GROUP_BY_THREAD);
0689: for (Line l = getFirstLine(); l != null; l = l.next()) {
0690: if (l instanceof MailboxLine) {
0691: MailboxLine line = (MailboxLine) l;
0692: MailboxEntry e = line.getMailboxEntry();
0693: // Only check date and size.
0694: if (e.getDate().equals(entry.getDate())) {
0695: if (e.getSize() == entry.getSize()) {
0696: // Found it!
0697: Log
0698: .debug("findLineForEntry date/size match");
0699: return line;
0700: }
0701: } else if (!groupByThread) {
0702: if (RFC822Date.compare(e.getDate(), entry
0703: .getDate()) > 0) {
0704: // We're past it (assuming mailbox is sorted by date).
0705: // The entry we're looking for was deleted. Return the
0706: // current line.
0707: return line;
0708: }
0709: } else if (line.next() == null) {
0710: // Last line of mailbox.
0711: return line;
0712: }
0713: }
0714: }
0715: }
0716: return null;
0717: }
0718:
0719: public int getLineNumberForEntry(MailboxEntry entry) {
0720: Line line = getLineForEntry(entry);
0721: if (line != null)
0722: return line.lineNumber();
0723: else
0724: return -1;
0725: }
0726:
0727: // Find first message that's not seen and not deleted.
0728: public Position getInitialDotPos() {
0729: if (getFirstLine() == null)
0730: return null;
0731: Line line = getFirstLine();
0732: while (true) {
0733: if (line instanceof MailboxLine) {
0734: MailboxEntry entry = ((MailboxLine) line)
0735: .getMailboxEntry();
0736: if (entry != null) {
0737: int flags = entry.getFlags();
0738: if ((flags & MailboxEntry.SEEN) == 0)
0739: if ((flags & MailboxEntry.DELETED) == 0)
0740: return new Position(line, 0);
0741: }
0742: }
0743: if (line.next() == null)
0744: break; // Reached last line.
0745: line = line.next();
0746: }
0747: return new Position(line, 0);
0748: }
0749:
0750: public MailboxEntry getInitialEntry() {
0751: if (getFirstLine() == null)
0752: return null;
0753: Line line = getFirstLine();
0754: while (true) {
0755: if (line instanceof MailboxLine) {
0756: MailboxEntry entry = ((MailboxLine) line)
0757: .getMailboxEntry();
0758: if (entry != null) {
0759: int flags = entry.getFlags();
0760: if ((flags & MailboxEntry.SEEN) == 0)
0761: if ((flags & MailboxEntry.DELETED) == 0)
0762: return entry;
0763: }
0764: }
0765: if (line.next() == null)
0766: break; // Reached last line.
0767: line = line.next();
0768: }
0769: return ((MailboxLine) line).getMailboxEntry();
0770: }
0771:
0772: public void updateEntry(MailboxEntry entry) {
0773: MailboxLine line = getLineForEntry(entry);
0774: if (line != null) {
0775: line.setText(entry.toString(line.getDepth()));
0776: Editor.updateInAllEditors(this , line);
0777: }
0778: }
0779:
0780: public final void toggleGroupByThread() {
0781: boolean groupByThread = getBooleanProperty(Property.GROUP_BY_THREAD);
0782: setProperty(Property.GROUP_BY_THREAD, !groupByThread);
0783: sort();
0784: }
0785:
0786: private void sort() {
0787: final Editor editor = Editor.currentEditor();
0788: // Remember where we are.
0789: final MailboxEntry currentEntry;
0790: if (editor.getDot() != null
0791: && editor.getDotLine() instanceof MailboxLine)
0792: currentEntry = ((MailboxLine) editor.getDotLine())
0793: .getMailboxEntry();
0794: else
0795: currentEntry = null;
0796: final Runnable completionRunnable = new Runnable() {
0797: public void run() {
0798: for (EditorIterator it = new EditorIterator(); it
0799: .hasNext();) {
0800: Editor ed = it.nextEditor();
0801: View view = new View();
0802: view
0803: .setDotEntry(currentEntry != null ? currentEntry
0804: : getInitialEntry());
0805: ed.setView(Mailbox.this , view);
0806: if (ed.getBuffer() == Mailbox.this ) {
0807: ed.bufferActivated(true);
0808: ed.updateDisplay();
0809: }
0810: }
0811: }
0812: };
0813: Runnable sortRunnable = new Runnable() {
0814: public void run() {
0815: editor.setWaitCursor();
0816: try {
0817: refreshBuffer();
0818: SwingUtilities.invokeLater(completionRunnable);
0819: } finally {
0820: unlock();
0821: setBusy(false);
0822: editor.setDefaultCursor();
0823: }
0824: }
0825: };
0826: if (lock()) {
0827: setBusy(true);
0828: new Thread(sortRunnable).start();
0829: }
0830: }
0831:
0832: public void foldThread(Line line) {
0833: if (!getBooleanProperty(Property.GROUP_BY_THREAD))
0834: return;
0835: if (line instanceof MailboxLine) {
0836: MailboxLine begin = (MailboxLine) line;
0837: // Find start of thread.
0838: while (begin.getDepth() > 1) {
0839: Line prev = begin.previous();
0840: if (prev instanceof MailboxLine)
0841: begin = (MailboxLine) prev;
0842: else
0843: break;
0844: }
0845: if (begin.next() instanceof MailboxLine) {
0846: MailboxLine end = (MailboxLine) begin.next();
0847: while (end.getDepth() > 1) {
0848: Line next = end.next();
0849: if (next instanceof MailboxLine)
0850: end = (MailboxLine) next;
0851: else
0852: break;
0853: }
0854: for (Line toBeHidden = begin.next(); toBeHidden != end
0855: && toBeHidden != null; toBeHidden = toBeHidden
0856: .next())
0857: toBeHidden.hide();
0858: renumber();
0859: Editor.unhideDotInAllFrames(this );
0860: }
0861: }
0862: }
0863:
0864: public void foldThreads() {
0865: if (!getBooleanProperty(Property.GROUP_BY_THREAD))
0866: return;
0867: for (Line line = getFirstLine(); line != null; line = line
0868: .nextVisible()) {
0869: if (line instanceof MailboxLine) {
0870: MailboxLine begin = (MailboxLine) line;
0871: if (begin.getDepth() == 1) {
0872: if (begin.next() instanceof MailboxLine) {
0873: MailboxLine end = (MailboxLine) begin.next();
0874: while (end.getDepth() > 1) {
0875: Line next = end.next();
0876: if (next instanceof MailboxLine)
0877: end = (MailboxLine) next;
0878: else
0879: break;
0880: }
0881: for (Line toBeHidden = begin.next(); toBeHidden != end
0882: && toBeHidden != null; toBeHidden = toBeHidden
0883: .next())
0884: toBeHidden.hide();
0885: }
0886: }
0887: }
0888: }
0889: renumber();
0890: Editor.unhideDotInAllFrames(this );
0891: }
0892:
0893: protected void refreshBuffer() {
0894: if (getBooleanProperty(Property.GROUP_BY_THREAD)) {
0895: long start = System.currentTimeMillis();
0896: SortByThread sort = new SortByThread(entries);
0897: sort.run();
0898: try {
0899: lockWrite();
0900: } catch (InterruptedException e) {
0901: Log.error(e);
0902: return;
0903: }
0904: try {
0905: synchronized (this ) {
0906: empty();
0907: sort.addEntries(this , limitFilter);
0908: renumber();
0909: countMessages();
0910: setLoaded(true);
0911: }
0912: } finally {
0913: unlockWrite();
0914: }
0915: long elapsed = System.currentTimeMillis() - start;
0916: Log.debug("refreshBuffer " + elapsed + " ms");
0917: } else {
0918: Debug.assertTrue(sortBy == SORT_BY_DATE_SENT);
0919: // Don't change order of entries!
0920: ArrayList temp = new ArrayList(entries);
0921: sortEntriesByDate(temp);
0922: List matchingEntries = getMatchingEntries(temp, limitFilter);
0923: try {
0924: lockWrite();
0925: } catch (InterruptedException e) {
0926: Log.error(e);
0927: return;
0928: }
0929: try {
0930: synchronized (this ) {
0931: empty();
0932: final int size = matchingEntries.size();
0933: for (int i = 0; i < size; i++)
0934: appendLine(((MailboxEntry) matchingEntries
0935: .get(i)));
0936: renumber();
0937: countMessages();
0938: setLoaded(true);
0939: }
0940: } finally {
0941: unlockWrite();
0942: }
0943: }
0944: }
0945:
0946: // Never returns null.
0947: private static List getMatchingEntries(List list,
0948: MailboxFilter filter) {
0949: if (list == null)
0950: return new ArrayList();
0951: if (filter == null)
0952: return new ArrayList(list);
0953: ArrayList matchingEntries = new ArrayList();
0954: final int size = list.size();
0955: for (int i = 0; i < size; i++) {
0956: MailboxEntry entry = (MailboxEntry) list.get(i);
0957: if (filter.accept(entry))
0958: matchingEntries.add(entry);
0959: }
0960: return matchingEntries;
0961: }
0962:
0963: public final void appendLine(MailboxEntry entry) {
0964: appendLine(new MailboxLine(entry));
0965: }
0966:
0967: public final void appendLine(MailboxEntry entry, int depth) {
0968: appendLine(new MailboxLine(entry, depth));
0969: }
0970:
0971: private static void sortEntriesByDate(List list) {
0972: Comparator c = new Comparator() {
0973: public int compare(Object o1, Object o2) {
0974: return RFC822Date.compare(
0975: ((MailboxEntry) o1).getDate(),
0976: ((MailboxEntry) o2).getDate());
0977: }
0978: };
0979: Collections.sort(list, c);
0980: int sequenceNumber = 1;
0981: Iterator iter = list.iterator();
0982: while (iter.hasNext()) {
0983: MailboxEntry entry = (MailboxEntry) iter.next();
0984: entry.setSequenceNumber(sequenceNumber++);
0985: }
0986: }
0987:
0988: protected void addEntriesToAddressBook(List list) {
0989: if (list == null)
0990: return;
0991: MailAddress userMailAddress = Mail.getUserMailAddress();
0992: AddressBook addressBook = AddressBook.getGlobalAddressBook();
0993: for (int i = 0; i < list.size(); i++) {
0994: MailboxEntry sourceEntry = (MailboxEntry) list.get(i);
0995: int flags = sourceEntry.getFlags();
0996: if ((flags & MailboxEntry.DELETED) != 0)
0997: continue;
0998: MailAddress[] from = sourceEntry.getFrom();
0999: MailAddress[] to = sourceEntry.getTo();
1000: MailAddress[] cc = sourceEntry.getCc();
1001: boolean add = false;
1002: if ((flags & MailboxEntry.ANSWERED) != 0) {
1003: // I replied to this message.
1004: add = true;
1005: } else if (from != null) {
1006: for (int j = 0; j < from.length; j++) {
1007: MailAddress a = from[j];
1008: if (a.matches(userMailAddress)) {
1009: // Message is from me.
1010: add = true;
1011: break;
1012: }
1013: }
1014: }
1015: if (!add) {
1016: if (to != null) {
1017: for (int j = 0; j < to.length; j++) {
1018: MailAddress a = to[j];
1019: if (a.matches(userMailAddress)) {
1020: // Message to me.
1021: add = true;
1022: break;
1023: }
1024: }
1025: }
1026: if (!add && cc != null) {
1027: for (int j = 0; j < cc.length; j++) {
1028: MailAddress a = cc[j];
1029: if (a.matches(userMailAddress)) {
1030: // Message copied to me.
1031: add = true;
1032: break;
1033: }
1034: }
1035: }
1036: }
1037: if (add) {
1038: if (to != null)
1039: for (int j = 0; j < to.length; j++)
1040: addressBook.maybeAddMailAddress(to[j]);
1041: if (cc != null)
1042: for (int j = 0; j < cc.length; j++)
1043: addressBook.maybeAddMailAddress(cc[j]);
1044: if (from != null)
1045: for (int j = 0; j < from.length; j++)
1046: addressBook.maybeAddMailAddress(from[j]);
1047: }
1048: }
1049: AddressBook.saveGlobalAddressBook();
1050: }
1051:
1052: // For the buffer list.
1053: public Icon getIcon() {
1054: return Utilities
1055: .getIconFromFile(newMessageCount > 0 ? "mailbox_new.png"
1056: : "mailbox.png");
1057: }
1058:
1059: public String getFileNameForDisplay() {
1060: return "";
1061: }
1062:
1063: protected void newMessagesStatus() {
1064: if (newMessageCount > 0) {
1065: FastStringBuffer sb = new FastStringBuffer(32);
1066: sb.append(String.valueOf(newMessageCount));
1067: sb.append(" new message");
1068: if (newMessageCount > 1)
1069: sb.append('s');
1070: status(sb.toString());
1071: } else
1072: status("No new messages");
1073: }
1074:
1075: protected void status(final String s) {
1076: Runnable r = new Runnable() {
1077: public void run() {
1078: for (int i = 0; i < Editor.getFrameCount(); i++) {
1079: Editor ed = Editor.getFrame(i).getCurrentEditor();
1080: if (ed.getBuffer() == Mailbox.this )
1081: ed.status(s);
1082: }
1083: }
1084: };
1085: SwingUtilities.invokeLater(r);
1086: }
1087:
1088: public MailAddress getUserMailAddress() {
1089: String address = getStringProperty(Property.USER_MAIL_ADDRESS);
1090: if (address == null)
1091: return null;
1092: return new MailAddress(
1093: getStringProperty(Property.USER_FULL_NAME), address);
1094: }
1095:
1096: public boolean isChildVisible() {
1097: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
1098: Buffer buffer = it.nextEditor().getBuffer();
1099: if (buffer instanceof MessageBuffer)
1100: if (((MessageBuffer) buffer).getMailbox() == this )
1101: return true;
1102: }
1103: return false;
1104: }
1105:
1106: // Returns true if mailbox has been idle for the specified number of
1107: // seconds, depending on whether it's in the foreground or background.
1108: public boolean isIdle(int fg, int bg) {
1109: if (!isVisible() && !isChildVisible()) {
1110: // Mailbox is in the background.
1111: if (bg == 0)
1112: return false;
1113: else if (System.currentTimeMillis()
1114: - Dispatcher.getLastEventMillis() > bg * 1000)
1115: return true;
1116: else
1117: return false;
1118: } else {
1119: // Mailbox (or one of its messages) is in the foreground.
1120: if (fg == 0)
1121: return false;
1122: else if (System.currentTimeMillis()
1123: - Dispatcher.getLastEventMillis() > fg * 1000)
1124: return true;
1125: else
1126: return false;
1127: }
1128: }
1129:
1130: protected void error(final String text, final String title) {
1131: Runnable r = new Runnable() {
1132: public void run() {
1133: Editor editor = Editor.currentEditor();
1134: // Restore default cursor.
1135: setBusy(false);
1136: editor.updateDisplay();
1137: MessageDialog.showMessageDialog(editor, text, title);
1138: }
1139: };
1140: SwingUtilities.invokeLater(r);
1141: }
1142:
1143: protected void success(final String text) {
1144: Runnable r = new Runnable() {
1145: public void run() {
1146: Editor.currentEditor().status(text);
1147: }
1148: };
1149: SwingUtilities.invokeLater(r);
1150: }
1151:
1152: protected void saveDisplayState() {
1153: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
1154: Editor ed = it.nextEditor();
1155: if (ed.getBuffer() == this )
1156: ed.saveView();
1157: }
1158: }
1159:
1160: protected void updateDisplay() {
1161: SwingUtilities.invokeLater(updateDisplayRunnable);
1162: }
1163:
1164: private Runnable updateDisplayRunnable = new Runnable() {
1165: public void run() {
1166: invalidate();
1167: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
1168: Editor ed = it.nextEditor();
1169: if (ed.getBuffer() == Mailbox.this ) {
1170: View view = ed.getView(ed.getBuffer());
1171: if (view.getDotEntry() != null) {
1172: try {
1173: lockRead();
1174: } catch (InterruptedException e) {
1175: Log.error(e);
1176: return;
1177: }
1178: try {
1179: Line topLine = findLineForEntry(view
1180: .getTopEntry());
1181: if (topLine == null)
1182: topLine = getFirstLine();
1183: ed.setTopLine(topLine);
1184: Line dotLine = findLineForEntry(view
1185: .getDotEntry());
1186: if (dotLine != null) {
1187: int offset = view.getDotOffset();
1188: if (offset > dotLine.length())
1189: offset = dotLine.length();
1190: ed.setDot(dotLine, offset);
1191: ed.moveCaretToDotCol();
1192: } else {
1193: dotLine = getLastLine();
1194: if (dotLine != null) {
1195: Log
1196: .debug("updateDisplayRunnable setting dotLine to last line");
1197: ed.setDot(dotLine, 0);
1198: ed.moveCaretToDotCol();
1199: } else
1200: ed.setDot(null);
1201: }
1202: ed.setMark(null);
1203: } finally {
1204: unlockRead();
1205: }
1206: } else {
1207: Line firstLine = getFirstLine();
1208: if (firstLine != null) {
1209: ed.setDot(firstLine, 0);
1210: ed.moveCaretToDotCol();
1211: ed.setMark(null);
1212: ed.setTopLine(firstLine);
1213: } else {
1214: ed.setDot(null);
1215: ed.setMark(null);
1216: ed.setTopLine(null);
1217: }
1218: }
1219: ed.setUpdateFlag(REPAINT);
1220: ed.updateDisplay();
1221: }
1222: }
1223: Sidebar
1224: .setUpdateFlagInAllFrames(SIDEBAR_BUFFER_LIST_CHANGED);
1225: Sidebar.repaintBufferListInAllFrames();
1226: }
1227: };
1228:
1229: protected void advanceDot(final Line dotLine) {
1230: final Line nextLine = dotLine.next();
1231: if (nextLine != null)
1232: setDotLine(nextLine);
1233: }
1234:
1235: public void setDotEntry(MailboxEntry entry) {
1236: final MailboxLine line = findLineForEntry(entry);
1237: if (line != null && line.getMailboxEntry() == entry)
1238: setDotLine(line);
1239: }
1240:
1241: private void setDotLine(final Line line) {
1242: Runnable r = new Runnable() {
1243: public void run() {
1244: for (EditorIterator it = new EditorIterator(); it
1245: .hasNext();) {
1246: Editor ed = it.nextEditor();
1247: if (ed.getBuffer() == Mailbox.this ) {
1248: if (ed.getDot() != null) {
1249: ed.update(ed.getDotLine());
1250: ed.getDot().moveTo(line, 0);
1251: ed.update(line);
1252: ed.moveCaretToDotCol();
1253: ed.clearStatusText();
1254: ed.updateDisplay();
1255: }
1256: }
1257: }
1258: }
1259: };
1260: if (SwingUtilities.isEventDispatchThread())
1261: r.run();
1262: else
1263: SwingUtilities.invokeLater(r);
1264: }
1265:
1266: public String getStatusText(Editor editor) {
1267: FastStringBuffer sb = new FastStringBuffer();
1268: if (editor.getDot() != null) {
1269: sb.append("Message ");
1270: sb.append(String.valueOf(editor.getDotLineNumber() + 1));
1271: sb.append(" of ");
1272: sb.append(String.valueOf(getLineCount()));
1273: final int u = getUnreadMessageCount();
1274: if (u > 0) {
1275: final int n = getNewMessageCount();
1276: sb.append(" (");
1277: if (n > 0) {
1278: sb.append(n);
1279: sb.append(" new, ");
1280: }
1281: sb.append(u);
1282: sb.append(" unread)");
1283: }
1284: } else {
1285: sb.append("No messages");
1286: }
1287: return sb.toString();
1288: }
1289:
1290: public void saveView(Editor editor) {
1291: final View view = saveViewInternal(editor);
1292: final Line topLine = editor.getTopLine();
1293: if (topLine instanceof MailboxLine)
1294: view.setTopEntry(((MailboxLine) topLine).getMailboxEntry());
1295: if (editor.getDot() != null) {
1296: Line dotLine = editor.getDotLine();
1297: if (dotLine instanceof MailboxLine)
1298: view.setDotEntry(((MailboxLine) dotLine)
1299: .getMailboxEntry());
1300: }
1301: editor.setView(this , view);
1302: setLastView(view);
1303: }
1304:
1305: public void restoreView(Editor editor) {
1306: final Display display = editor.getDisplay();
1307: final View view = editor.getView(this );
1308: Debug.assertTrue(view != null);
1309: if (view != null) {
1310: if (view.getDotEntry() == null) {
1311: super .restoreView(editor);
1312: return;
1313: }
1314: Line topLine = findLineForEntry(view.getTopEntry());
1315: if (topLine == null)
1316: topLine = getFirstLine();
1317: display.setTopLine(topLine);
1318: Line dotLine = findLineForEntry(view.getDotEntry());
1319: if (dotLine == null)
1320: dotLine = getFirstLine();
1321: if (view.getTopLine() == topLine && view.getDot() != null
1322: && view.getDot().getLine() == dotLine) {
1323: editor.setDot(new Position(view.getDot()));
1324: editor.setMark(view.getMark() == null ? null
1325: : new Position(view.getMark()));
1326: editor.setSelection(view.getSelection());
1327: display.setTopLine(view.getTopLine());
1328: display.setShift(view.getShift());
1329: display.setCaretCol(view.getCaretCol());
1330: } else {
1331: editor
1332: .setDot(dotLine != null ? new Position(dotLine,
1333: 0) : null);
1334: editor.moveCaretToDotCol();
1335: editor.setMark(null);
1336: }
1337: }
1338: }
1339:
1340: protected void notImplemented(String s) {
1341: Log.error(s.concat(" is not implemented"));
1342: }
1343: }
|