#include "MailWindow.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <syslog.h>
#include <unistd.h>
#include <AppFileInfo.h>
#include <Autolock.h>
#include <Bitmap.h>
#include <Button.h>
#include <CharacterSet.h>
#include <CharacterSetRoster.h>
#include <Clipboard.h>
#include <ControlLook.h>
#include <Debug.h>
#include <E-mail.h>
#include <File.h>
#include <IconUtils.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <Node.h>
#include <PathMonitor.h>
#include <PrintJob.h>
#include <Query.h>
#include <Resources.h>
#include <Roster.h>
#include <Screen.h>
#include <String.h>
#include <StringList.h>
#include <StringView.h>
#include <TextView.h>
#include <UTF8.h>
#include <VolumeRoster.h>
#include <fs_index.h>
#include <fs_info.h>
#include <MailMessage.h>
#include <MailSettings.h>
#include <MailDaemon.h>
#include <mail_util.h>
#include <CharacterSetRoster.h>
#include "AttributeUtilities.h"
#include "Content.h"
#include "Enclosures.h"
#include "FieldMsg.h"
#include "FindWindow.h"
#include "Header.h"
#include "LabelWindow.h"
#include "Messages.h"
#include "MailApp.h"
#include "MailPopUpMenu.h"
#include "MailSupport.h"
#include "Prefs.h"
#include "QueryMenu.h"
#include "Signature.h"
#include "Settings.h"
#include "String.h"
#include "Utilities.h"
#define B_TRANSLATION_CONTEXT "Mail"
using namespace BPrivate;
const char* kUndoStrings[] = {
"Undo",
"Undo typing",
"Undo cut",
"Undo paste",
"Undo clear",
"Undo drop"
};
const char* kRedoStrings[] = {
"Redo",
"Redo typing",
"Redo cut",
"Redo paste",
"Redo clear",
"Redo drop"
};
static const char* kSpamMenuItemTextArray[] = {
"Mark as spam and move to trash",
"Mark as spam",
"Unmark this message",
"Mark as genuine"
};
static const char* kQueriesDirectory = "mail/queries";
static const char* kAttrQueryInitialMode = "_trk/qryinitmode";
static const char* kAttrQueryInitialString = "_trk/qryinitstr";
static const char* kAttrQueryInitialNumAttrs = "_trk/qryinitnumattrs";
static const char* kAttrQueryInitialAttrs = "_trk/qryinitattrs";
static const uint32 kAttributeItemMain = 'Fatr';
static const uint32 kByNameItem = 'Fbyn';
static const uint32 kByAttributeItem = 'Fbya';
static const uint32 kByForumlaItem = 'Fbyq';
static const int kCopyBufferSize = 64 * 1024;
static const char* kSameLabelItem = B_TRANSLATE("Same label");
static const char* kSameRecipientItem = B_TRANSLATE("Same recipient");
static const char* kSameSenderItem = B_TRANSLATE("Same sender");
static const char* kSameSubjectItem = B_TRANSLATE("Same subject");
BObjectList<TMailWindow::BitmapItem> TMailWindow::sBitmapCache;
BLocker TMailWindow::sBitmapCacheLock;
BList TMailWindow::sWindowList;
BLocker TMailWindow::sWindowListLock;
class HorizontalLine : public BView {
public:
HorizontalLine(BRect rect)
:
BView (rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW)
{
}
virtual void Draw(BRect rect)
{
FillRect(rect, B_SOLID_HIGH);
}
};
TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app,
const entry_ref* ref, const char* to, const BFont* font, bool resending,
BMessenger* messenger)
:
BWindow(rect, title, B_DOCUMENT_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS),
fApp(app),
fMail(NULL),
fRef(NULL),
fFieldState(0),
fPanel(NULL),
fSaveAddrMenu(NULL),
fLabelMenu(NULL),
fLeaveStatusMenu(NULL),
fEncodingMenu(NULL),
fLabel(NULL),
fZoom(rect),
fEnclosuresView(NULL),
fPrevTrackerPositionSaved(false),
fNextTrackerPositionSaved(false),
fSigAdded(false),
fReplying(false),
fResending(resending),
fSent(false),
fDraft(false),
fChanged(false),
fOriginatingWindow(NULL),
fDownloading(false)
{
fKeepStatusOnClose = false;
if (messenger != NULL)
fTrackerMessenger = *messenger;
BFile file(ref, B_READ_ONLY);
if (ref) {
fRef = new entry_ref(*ref);
fIncoming = true;
} else
fIncoming = false;
fAutoMarkRead = fApp->AutoMarkRead();
fMenuBar = new BMenuBar("menuBar");
BMenu* menu = new BMenu(B_TRANSLATE("File"));
BMessage* msg = new BMessage(M_NEW);
msg->AddInt32("type", M_NEW);
BMenuItem* item = new BMenuItem(B_TRANSLATE("New mail message"), msg, 'N');
menu->AddItem(item);
item->SetTarget(be_app);
if (!fIncoming) {
QueryMenu* queryMenu = new QueryMenu(B_TRANSLATE("Open draft"), false);
queryMenu->SetTargetForItems(be_app);
queryMenu->SetPredicate("MAIL:draft==1");
menu->AddItem(queryMenu);
}
if (!fIncoming || resending) {
menu->AddItem(fSendLater = new BMenuItem(B_TRANSLATE("Save as draft"),
new BMessage(M_SAVE_AS_DRAFT), 'S'));
}
if (!resending && fIncoming) {
menu->AddSeparatorItem();
BMenu* subMenu = new BMenu(B_TRANSLATE("Close and "));
read_flags flag;
read_read_attr(file, flag);
if (flag == B_UNREAD) {
subMenu->AddItem(new BMenuItem(
B_TRANSLATE_COMMENT("Leave as 'New'",
"Do not translate New - this is non-localizable e-mail status"),
new BMessage(kMsgCloseAndKeepAllStatus), 'W', B_SHIFT_KEY));
} else {
BString status;
file.ReadAttrString(B_MAIL_ATTR_STATUS, &status);
BString label;
if (status.Length() > 0)
label.SetToFormat(B_TRANSLATE("Leave as '%s'"),
status.String());
else
label = B_TRANSLATE("Leave same");
subMenu->AddItem(new BMenuItem(label.String(),
new BMessage(B_QUIT_REQUESTED), 'W'));
AddShortcut('W', B_COMMAND_KEY | B_SHIFT_KEY,
new BMessage(kMsgCloseAndKeepAllStatus));
}
subMenu->AddItem(new BMenuItem(B_TRANSLATE("Move to trash"),
new BMessage(M_DELETE), 'T', B_CONTROL_KEY));
AddShortcut('T', B_SHIFT_KEY | B_COMMAND_KEY,
new BMessage(M_DELETE_NEXT));
menu->AddItem(subMenu);
fLeaveStatusMenu = subMenu;
} else {
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
new BMessage(B_CLOSE_REQUESTED), 'W'));
}
file.ReadAttrString(B_MAIL_ATTR_LABEL, &fLabel);
if (fLabel.Length() > 0)
create_label_file(fLabel);
fLabelMenu = new BMenu(B_TRANSLATE("Set label"));
menu->AddItem(fLabelMenu);
menu->AddSeparatorItem();
menu->AddItem(fPrint = new BMenuItem(
B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS),
new BMessage(M_PRINT_SETUP)));
menu->AddItem(fPrint = new BMenuItem(
B_TRANSLATE("Print" B_UTF8_ELLIPSIS),
new BMessage(M_PRINT), 'P'));
fMenuBar->AddItem(menu);
menu->AddSeparatorItem();
menu->AddItem(item = new BMenuItem(B_TRANSLATE("Quit"),
new BMessage(B_QUIT_REQUESTED), 'Q'));
item->SetTarget(be_app);
menu = new BMenu(B_TRANSLATE("Edit"));
menu->AddItem(fUndo = new BMenuItem(B_TRANSLATE("Undo"),
new BMessage(B_UNDO), 'Z', 0));
fUndo->SetTarget(NULL, this);
menu->AddItem(fRedo = new BMenuItem(B_TRANSLATE("Redo"),
new BMessage(M_REDO), 'Z', B_SHIFT_KEY));
fRedo->SetTarget(NULL, this);
menu->AddSeparatorItem();
menu->AddItem(fCut = new BMenuItem(B_TRANSLATE("Cut"),
new BMessage(B_CUT), 'X'));
fCut->SetTarget(NULL, this);
menu->AddItem(fCopy = new BMenuItem(B_TRANSLATE("Copy"),
new BMessage(B_COPY), 'C'));
fCopy->SetTarget(NULL, this);
menu->AddItem(fPaste = new BMenuItem(B_TRANSLATE("Paste"),
new BMessage(B_PASTE),
'V'));
fPaste->SetTarget(NULL, this);
menu->AddSeparatorItem();
menu->AddItem(item = new BMenuItem(B_TRANSLATE("Select all"),
new BMessage(M_SELECT), 'A'));
menu->AddSeparatorItem();
item->SetTarget(NULL, this);
menu->AddItem(new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS),
new BMessage(M_FIND), 'F'));
menu->AddItem(new BMenuItem(B_TRANSLATE("Find again"),
new BMessage(M_FIND_AGAIN), 'G'));
if (!fIncoming) {
menu->AddSeparatorItem();
fQuote = new BMenuItem(B_TRANSLATE("Increase quote level"),
new BMessage(M_ADD_QUOTE_LEVEL), '+');
menu->AddItem(fQuote);
fRemoveQuote = new BMenuItem(B_TRANSLATE("Decrease quote level"),
new BMessage(M_SUB_QUOTE_LEVEL), '-');
menu->AddItem(fRemoveQuote);
menu->AddSeparatorItem();
fSpelling = new BMenuItem(B_TRANSLATE("Check spelling"),
new BMessage(M_CHECK_SPELLING), ';');
menu->AddItem(fSpelling);
if (fApp->StartWithSpellCheckOn())
PostMessage(M_CHECK_SPELLING);
}
menu->AddSeparatorItem();
menu->AddItem(item = new BMenuItem(
B_TRANSLATE("Settings" B_UTF8_ELLIPSIS),
new BMessage(M_PREFS), ','));
item->SetTarget(be_app);
fMenuBar->AddItem(menu);
menu->AddItem(item = new BMenuItem(
B_TRANSLATE("Accounts" B_UTF8_ELLIPSIS),
new BMessage(M_ACCOUNTS)));
item->SetTarget(be_app);
if (!resending && fIncoming) {
menu = new BMenu(B_TRANSLATE("View"));
menu->AddItem(fHeader = new BMenuItem(B_TRANSLATE("Show header"),
new BMessage(M_HEADER), 'H'));
menu->AddItem(fRaw = new BMenuItem(B_TRANSLATE("Show raw message"),
new BMessage(M_RAW)));
fMenuBar->AddItem(menu);
}
menu = new BMenu(B_TRANSLATE("Message"));
if (!resending && fIncoming) {
menu->AddItem(new BMenuItem(B_TRANSLATE("Reply"),
new BMessage(M_REPLY),'R'));
menu->AddItem(new BMenuItem(B_TRANSLATE("Reply to sender"),
new BMessage(M_REPLY_TO_SENDER),'R',B_OPTION_KEY));
menu->AddItem(new BMenuItem(B_TRANSLATE("Reply to all"),
new BMessage(M_REPLY_ALL), 'R', B_SHIFT_KEY));
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem(B_TRANSLATE("Forward"),
new BMessage(M_FORWARD), 'J'));
menu->AddItem(new BMenuItem(B_TRANSLATE("Forward without attachments"),
new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS)));
menu->AddItem(new BMenuItem(B_TRANSLATE("Resend"),
new BMessage(M_RESEND)));
menu->AddItem(new BMenuItem(B_TRANSLATE("Copy to new"),
new BMessage(M_COPY_TO_NEW), 'D'));
menu->AddSeparatorItem();
fDeleteNext = new BMenuItem(B_TRANSLATE("Move to trash"),
new BMessage(M_DELETE_NEXT), 'T');
menu->AddItem(fDeleteNext);
menu->AddSeparatorItem();
BMessage* prevMsg = new BMessage(M_PREVMSG);
prevMsg->AddBool("keepStatus", false);
fPrevMsg = new BMenuItem(B_TRANSLATE("Previous message"), prevMsg, B_UP_ARROW);
menu->AddItem(fPrevMsg);
BMessage* nextMsg = new BMessage(M_NEXTMSG);
nextMsg->AddBool("keepStatus", false);
fNextMsg = new BMenuItem(B_TRANSLATE("Next message"), nextMsg, B_DOWN_ARROW);
menu->AddItem(fNextMsg);
} else {
menu->AddItem(fSendNow = new BMenuItem(B_TRANSLATE("Send message"),
new BMessage(M_SEND_NOW), 'M'));
if (!fIncoming) {
menu->AddSeparatorItem();
fSignature = new TMenu(B_TRANSLATE("Add signature"),
INDEX_SIGNATURE, M_SIGNATURE);
menu->AddItem(new BMenuItem(fSignature));
menu->AddItem(item = new BMenuItem(
B_TRANSLATE("Edit signatures" B_UTF8_ELLIPSIS),
new BMessage(M_EDIT_SIGNATURE)));
item->SetTarget(be_app);
menu->AddSeparatorItem();
menu->AddItem(fAdd = new BMenuItem(
B_TRANSLATE("Add attachment" B_UTF8_ELLIPSIS),
new BMessage(M_ADD), 'E'));
menu->AddItem(fRemove = new BMenuItem(
B_TRANSLATE("Remove attachment"),
new BMessage(M_REMOVE), 'T'));
}
}
if (fIncoming) {
menu->AddSeparatorItem();
fSaveAddrMenu = new BMenu(B_TRANSLATE("Save address"));
menu->AddItem(fSaveAddrMenu);
}
fEncodingMenu = new BMenu(B_TRANSLATE("Encoding"));
BMenuItem* automaticItem = NULL;
if (!resending && fIncoming) {
msg = new BMessage(CHARSET_CHOICE_MADE);
msg->AddInt32("charset", B_MAIL_NULL_CONVERSION);
automaticItem = new BMenuItem(B_TRANSLATE("Automatic"), msg);
fEncodingMenu->AddItem(automaticItem);
fEncodingMenu->AddSeparatorItem();
}
uint32 defaultCharSet = resending || !fIncoming
? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION;
bool markedCharSet = false;
BCharacterSetRoster roster;
BCharacterSet charSet;
while (roster.GetNextCharacterSet(&charSet) == B_OK) {
BString name(charSet.GetPrintName());
const char* mime = charSet.GetMIMEName();
if (mime != NULL)
name << " (" << mime << ")";
uint32 convertID;
if (mime == NULL || strcasecmp(mime, "UTF-8") != 0)
convertID = charSet.GetConversionID();
else
convertID = B_MAIL_UTF8_CONVERSION;
msg = new BMessage(CHARSET_CHOICE_MADE);
msg->AddInt32("charset", convertID);
fEncodingMenu->AddItem(item = new BMenuItem(name.String(), msg));
if (convertID == defaultCharSet && !markedCharSet) {
item->SetMarked(true);
markedCharSet = true;
}
}
msg = new BMessage(CHARSET_CHOICE_MADE);
msg->AddInt32("charset", B_MAIL_US_ASCII_CONVERSION);
fEncodingMenu->AddItem(item = new BMenuItem("US-ASCII", msg));
if (defaultCharSet == B_MAIL_US_ASCII_CONVERSION && !markedCharSet) {
item->SetMarked(true);
markedCharSet = true;
}
if (automaticItem != NULL && !markedCharSet)
automaticItem->SetMarked(true);
menu->AddSeparatorItem();
menu->AddItem(fEncodingMenu);
fMenuBar->AddItem(menu);
fEncodingMenu->SetRadioMode(true);
fEncodingMenu->SetTargetForItems(this);
if (!resending && fIncoming && fApp->ShowSpamGUI()) {
menu = new BMenu("Spam filtering");
menu->AddItem(new BMenuItem("Mark as spam and move to trash",
new BMessage(M_TRAIN_SPAM_AND_DELETE), 'K'));
menu->AddItem(new BMenuItem("Mark as spam",
new BMessage(M_TRAIN_SPAM), 'K', B_OPTION_KEY));
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem("Unmark this message",
new BMessage(M_UNTRAIN)));
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem("Mark as genuine",
new BMessage(M_TRAIN_GENUINE), 'K', B_SHIFT_KEY));
fMenuBar->AddItem(menu);
}
fQueryMenu = new BMenu(B_TRANSLATE("Queries"));
fMenuBar->AddItem(fQueryMenu);
_RebuildQueryMenu(true);
BuildToolBar();
if (!fApp->ShowToolBar())
fToolBar->Hide();
fHeaderView = new THeaderView(fIncoming, resending,
fApp->DefaultAccount());
fContentView = new TContentView(fIncoming, const_cast<BFont*>(font),
false, fApp->ColoredQuotes());
BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
.Add(fMenuBar)
.Add(fToolBar)
.AddGroup(B_VERTICAL, 0)
.Add(fHeaderView)
.SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING)
.End()
.Add(fContentView);
if (to != NULL)
fHeaderView->SetTo(to);
AddShortcut('n', B_COMMAND_KEY, new BMessage(M_NEW));
BString signature = fApp->Signature();
if (!fIncoming && strcmp(signature.String(), B_TRANSLATE("None")) != 0) {
if (strcmp(signature.String(), B_TRANSLATE("Random")) == 0)
PostMessage(M_RANDOM_SIG);
else {
BVolume volume;
BVolumeRoster().GetBootVolume(&volume);
BQuery query;
query.SetVolume(&volume);
query.PushAttr(INDEX_SIGNATURE);
query.PushString(signature.String());
query.PushOp(B_EQ);
query.Fetch();
BEntry entry;
if (query.GetNextEntry(&entry) == B_NO_ERROR) {
BFile file;
file.SetTo(&entry, O_RDWR);
if (file.InitCheck() == B_NO_ERROR) {
entry_ref ref;
entry.GetRef(&ref);
BMessage msg(M_SIGNATURE);
msg.AddRef("ref", &ref);
PostMessage(&msg);
}
} else {
char tempString [2048];
query.GetPredicate (tempString, sizeof (tempString));
printf ("Query failed, was looking for: %s\n", tempString);
}
}
}
OpenMessage(ref, _CurrentCharacterSet());
AddShortcut('q', B_SHIFT_KEY, new BMessage(kMsgQuitAndKeepAllStatus));
}
BBitmap*
TMailWindow::_RetrieveVectorIcon(int32 id)
{
BAutolock lock(sBitmapCacheLock);
if (!lock.IsLocked())
return NULL;
BitmapItem* item;
for (int32 i = 0; (item = sBitmapCache.ItemAt(i)) != NULL; i++) {
if (item->id == id)
return item->bm;
}
BResources* res = BApplication::AppResources();
if (res == NULL)
return NULL;
size_t size;
const void* data = res->LoadResource(B_VECTOR_ICON_TYPE, id, &size);
if (!data)
return NULL;
BBitmap* bitmap = new BBitmap(BRect(BPoint(0, 0),
be_control_look->ComposeIconSize(22)), B_RGBA32);
status_t status = BIconUtils::GetVectorIcon((uint8*)data, size, bitmap);
if (status == B_OK) {
item = (BitmapItem*)malloc(sizeof(BitmapItem));
item->bm = bitmap;
item->id = id;
sBitmapCache.AddItem(item);
return bitmap;
}
return NULL;
}
void
TMailWindow::BuildToolBar()
{
fToolBar = new BToolBar();
fToolBar->AddAction(M_NEW, this, _RetrieveVectorIcon(11), NULL,
B_TRANSLATE("New"));
fToolBar->AddSeparator();
if (fResending) {
fToolBar->AddAction(M_SEND_NOW, this, _RetrieveVectorIcon(1), NULL,
B_TRANSLATE("Send"));
} else if (!fIncoming) {
fToolBar->AddAction(M_SEND_NOW, this, _RetrieveVectorIcon(1), NULL,
B_TRANSLATE("Send"));
fToolBar->SetActionEnabled(M_SEND_NOW, false);
fToolBar->AddAction(M_SIG_MENU, this, _RetrieveVectorIcon(2), NULL,
B_TRANSLATE("Signature"));
fToolBar->AddAction(M_SAVE_AS_DRAFT, this, _RetrieveVectorIcon(3), NULL,
B_TRANSLATE("Save"));
fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false);
fToolBar->AddAction(M_PRINT, this, _RetrieveVectorIcon(5), NULL,
B_TRANSLATE("Print"));
fToolBar->SetActionEnabled(M_PRINT, false);
fToolBar->AddAction(M_DELETE, this, _RetrieveVectorIcon(4), NULL,
B_TRANSLATE("Trash"));
} else {
fToolBar->AddAction(M_REPLY, this, _RetrieveVectorIcon(8), NULL,
B_TRANSLATE("Reply"));
fToolBar->AddAction(M_FORWARD, this, _RetrieveVectorIcon(9), NULL,
B_TRANSLATE("Forward"));
fToolBar->AddAction(M_PRINT, this, _RetrieveVectorIcon(5), NULL,
B_TRANSLATE("Print"));
fToolBar->AddAction(M_DELETE_NEXT, this, _RetrieveVectorIcon(4), NULL,
B_TRANSLATE("Trash"));
if (fApp->ShowSpamGUI()) {
fToolBar->AddAction(M_SPAM_BUTTON, this, _RetrieveVectorIcon(10),
NULL, B_TRANSLATE("Spam"));
}
fToolBar->AddSeparator();
fToolBar->AddAction(M_NEXTMSG, this, _RetrieveVectorIcon(6), NULL,
B_TRANSLATE("Next"));
fToolBar->AddAction(M_UNREAD, this, _RetrieveVectorIcon(12), NULL,
B_TRANSLATE("Unread"));
fToolBar->SetActionVisible(M_UNREAD, false);
fToolBar->AddAction(M_READ, this, _RetrieveVectorIcon(13), NULL,
B_TRANSLATE(" Read "));
fToolBar->SetActionVisible(M_READ, false);
fToolBar->AddAction(M_PREVMSG, this, _RetrieveVectorIcon(7), NULL,
B_TRANSLATE("Previous"));
if (!fTrackerMessenger.IsValid()) {
fToolBar->SetActionEnabled(M_NEXTMSG, false);
fToolBar->SetActionEnabled(M_PREVMSG, false);
}
if (!fAutoMarkRead)
_AddReadButton();
}
fToolBar->AddGlue();
}
void
TMailWindow::UpdateViews()
{
uint8 showToolBar = fApp->ShowToolBar();
if (showToolBar) {
if (fToolBar->IsHidden())
fToolBar->Show();
bool showLabel = showToolBar == kShowToolBar;
_UpdateLabel(M_NEW, B_TRANSLATE("New"), showLabel);
_UpdateLabel(M_SEND_NOW, B_TRANSLATE("Send"), showLabel);
_UpdateLabel(M_SIG_MENU, B_TRANSLATE("Signature"), showLabel);
_UpdateLabel(M_SAVE_AS_DRAFT, B_TRANSLATE("Save"), showLabel);
_UpdateLabel(M_PRINT, B_TRANSLATE("Print"), showLabel);
_UpdateLabel(M_DELETE, B_TRANSLATE("Trash"), showLabel);
_UpdateLabel(M_REPLY, B_TRANSLATE("Reply"), showLabel);
_UpdateLabel(M_FORWARD, B_TRANSLATE("Forward"), showLabel);
_UpdateLabel(M_DELETE_NEXT, B_TRANSLATE("Trash"), showLabel);
_UpdateLabel(M_SPAM_BUTTON, B_TRANSLATE("Spam"), showLabel);
_UpdateLabel(M_NEXTMSG, B_TRANSLATE("Next"), showLabel);
_UpdateLabel(M_UNREAD, B_TRANSLATE("Unread"), showLabel);
_UpdateLabel(M_READ, B_TRANSLATE(" Read "), showLabel);
_UpdateLabel(M_PREVMSG, B_TRANSLATE("Previous"), showLabel);
} else if (!fToolBar->IsHidden())
fToolBar->Hide();
}
void
TMailWindow::UpdatePreferences()
{
fAutoMarkRead = fApp->AutoMarkRead();
_UpdateReadButton();
}
TMailWindow::~TMailWindow()
{
fApp->SetLastWindowFrame(Frame());
delete fMail;
delete fPanel;
delete fOriginatingWindow;
delete fRef;
BAutolock locker(sWindowListLock);
sWindowList.RemoveItem(this);
}
status_t
TMailWindow::GetMailNodeRef(node_ref& nodeRef) const
{
if (fRef == NULL)
return B_ERROR;
BNode node(fRef);
return node.GetNodeRef(&nodeRef);
}
bool
TMailWindow::GetTrackerWindowFile(entry_ref* ref, bool next) const
{
if (next && fNextTrackerPositionSaved) {
*ref = fNextRef;
return true;
}
if (!next && fPrevTrackerPositionSaved) {
*ref = fPrevRef;
return true;
}
if (!fTrackerMessenger.IsValid())
return false;
entry_ref nextRef = *ref;
bool foundRef = false;
while (!foundRef) {
BMessage request(B_GET_PROPERTY);
BMessage spc;
if (next)
spc.what = 'snxt';
else
spc.what = 'sprv';
spc.AddString("property", "Entry");
spc.AddRef("data", &nextRef);
request.AddSpecifier(&spc);
BMessage reply;
if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK)
return false;
if (reply.FindRef("result", &nextRef) != B_OK)
return false;
char fileType[256];
BNode node(&nextRef);
if (node.InitCheck() != B_OK)
return false;
if (BNodeInfo(&node).GetType(fileType) != B_OK)
return false;
if (strcasecmp(fileType, B_MAIL_TYPE) == 0
|| strcasecmp(fileType, B_PARTIAL_MAIL_TYPE) == 0)
foundRef = true;
}
*ref = nextRef;
return foundRef;
}
void
TMailWindow::SaveTrackerPosition(entry_ref* ref)
{
if (fNextTrackerPositionSaved || fPrevTrackerPositionSaved)
return;
fNextRef = fPrevRef = *ref;
fNextTrackerPositionSaved = GetTrackerWindowFile(&fNextRef, true);
fPrevTrackerPositionSaved = GetTrackerWindowFile(&fPrevRef, false);
}
void
TMailWindow::SetOriginatingWindow(BWindow* window)
{
delete fOriginatingWindow;
fOriginatingWindow = new BMessenger(window);
}
void
TMailWindow::SetTrackerSelectionToCurrent()
{
BMessage setSelection(B_SET_PROPERTY);
setSelection.AddSpecifier("Selection");
setSelection.AddRef("data", fRef);
fTrackerMessenger.SendMessage(&setSelection);
}
void
TMailWindow::PreserveReadingPos(bool save)
{
BScrollBar* scroll = fContentView->TextView()->ScrollBar(B_VERTICAL);
if (scroll == NULL || fRef == NULL)
return;
BNode node(fRef);
float pos = scroll->Value();
const char* name = "MAIL:read_pos";
if (save) {
node.WriteAttr(name, B_FLOAT_TYPE, 0, &pos, sizeof(pos));
return;
}
if (node.ReadAttr(name, B_FLOAT_TYPE, 0, &pos, sizeof(pos)) == sizeof(pos)) {
Lock();
scroll->SetValue(pos);
Unlock();
}
}
void
TMailWindow::MarkMessageRead(entry_ref* message, read_flags flag)
{
BNode node(message);
status_t status = node.InitCheck();
if (status != B_OK)
return;
int32 account;
if (node.ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &account,
sizeof(account)) < 0)
account = -1;
write_read_attr(node, flag);
PreserveReadingPos(true);
BMailDaemon().MarkAsRead(account, *message, flag);
}
void
TMailWindow::FrameResized(float width, float height)
{
fContentView->FrameResized(width, height);
}
void
TMailWindow::MenusBeginning()
{
int32 finish = 0;
int32 start = 0;
if (!fIncoming) {
bool gotToField = !fHeaderView->IsToEmpty();
bool gotCcField = !fHeaderView->IsCcEmpty();
bool gotBccField = !fHeaderView->IsBccEmpty();
bool gotSubjectField = !fHeaderView->IsSubjectEmpty();
bool gotText = fContentView->TextView()->Text()[0] != 0;
fSendNow->SetEnabled(gotToField || gotBccField);
fSendLater->SetEnabled(fChanged && (gotToField || gotCcField
|| gotBccField || gotSubjectField || gotText));
be_clipboard->Lock();
fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain",
B_MIME_TYPE)
&& (fEnclosuresView == NULL || !fEnclosuresView->fList->IsFocus()));
be_clipboard->Unlock();
fQuote->SetEnabled(false);
fRemoveQuote->SetEnabled(false);
fAdd->SetEnabled(true);
fRemove->SetEnabled(fEnclosuresView != NULL
&& fEnclosuresView->fList->CurrentSelection() >= 0);
} else {
if (fResending) {
bool enable = !fHeaderView->IsToEmpty();
fSendNow->SetEnabled(enable);
if (fHeaderView->ToControl()->HasFocus()) {
fHeaderView->ToControl()->GetSelection(&start, &finish);
fCut->SetEnabled(start != finish);
be_clipboard->Lock();
fPaste->SetEnabled(be_clipboard->Data()->HasData(
"text/plain", B_MIME_TYPE));
be_clipboard->Unlock();
} else {
fCut->SetEnabled(false);
fPaste->SetEnabled(false);
}
} else {
fCut->SetEnabled(false);
fPaste->SetEnabled(false);
if (modifiers() & B_SHIFT_KEY) {
fPrevMsg->SetLabel(B_TRANSLATE("Previous message, keep status"));
fPrevMsg->SetShortcut(B_UP_ARROW, B_SHIFT_KEY);
BMessage* prevMsg = new BMessage(M_PREVMSG);
prevMsg->AddBool("keepStatus", true);
fPrevMsg->SetMessage(prevMsg);
fNextMsg->SetLabel(B_TRANSLATE("Next message, keep status"));
fNextMsg->SetShortcut(B_DOWN_ARROW, B_SHIFT_KEY);
BMessage* nextMsg = new BMessage(M_NEXTMSG);
nextMsg->AddBool("keepStatus", true);
fNextMsg->SetMessage(nextMsg);
} else {
fPrevMsg->SetLabel(B_TRANSLATE("Previous message"));
fPrevMsg->SetShortcut(B_UP_ARROW, 0);
BMessage* prevMsg = new BMessage(M_PREVMSG);
prevMsg->AddBool("keepStatus", false);
fPrevMsg->SetMessage(prevMsg);
fNextMsg->SetLabel(B_TRANSLATE("Next message"));
fNextMsg->SetShortcut(B_DOWN_ARROW, 0);
BMessage* nextMsg = new BMessage(M_NEXTMSG);
nextMsg->AddBool("keepStatus", false);
fNextMsg->SetMessage(nextMsg);
}
}
}
fPrint->SetEnabled(fContentView->TextView()->TextLength());
BTextView* textView = dynamic_cast<BTextView*>(CurrentFocus());
if (textView != NULL
&& (dynamic_cast<AddressTextControl*>(textView->Parent()) != NULL
|| dynamic_cast<BTextControl*>(textView->Parent()) != NULL)) {
textView->GetSelection(&start, &finish);
} else if (fContentView->TextView()->IsFocus()) {
fContentView->TextView()->GetSelection(&start, &finish);
if (!fIncoming) {
fQuote->SetEnabled(true);
fRemoveQuote->SetEnabled(true);
}
}
fCopy->SetEnabled(start != finish);
if (!fIncoming)
fCut->SetEnabled(start != finish);
bool isRedo = false;
undo_state undoState = B_UNDO_UNAVAILABLE;
BTextView* focusTextView = dynamic_cast<BTextView*>(CurrentFocus());
if (focusTextView != NULL)
undoState = focusTextView->UndoState(&isRedo);
fUndo->SetEnabled(undoState != B_UNDO_UNAVAILABLE);
if (fLeaveStatusMenu != NULL && fRef != NULL) {
BFile file(fRef, B_READ_ONLY);
BString status;
file.ReadAttrString(B_MAIL_ATTR_STATUS, &status);
BMenuItem* LeaveStatus = fLeaveStatusMenu->FindItem(B_QUIT_REQUESTED);
if (LeaveStatus == NULL)
LeaveStatus = fLeaveStatusMenu->FindItem(kMsgCloseAndKeepAllStatus);
if (LeaveStatus != NULL && status.Length() > 0) {
BString label;
label.SetToFormat(B_TRANSLATE("Leave as '%s'"), status.String());
LeaveStatus->SetLabel(label.String());
}
}
if (fRef != NULL) {
BFile file(fRef, B_READ_ONLY);
BString currentLabel;
file.ReadAttrString(B_MAIL_ATTR_LABEL, ¤tLabel);
BMenuItem* menuItem = new BMenuItem(B_TRANSLATE("Remove label"),
new BMessage(M_REMOVE_LABEL), '0');
fLabelMenu->AddItem(menuItem);
fLabelMenu->AddItem(new BMenuItem(B_TRANSLATE("Label as" B_UTF8_ELLIPSIS),
new BMessage(M_NEW_LABEL)));
fLabelMenu->AddSeparatorItem();
if (add_folder_menu_items(fLabelMenu, "labels", M_SET_LABEL) > 0)
fLabelMenu->AddSeparatorItem();
fLabelMenu->AddItem(new BMenuItem(B_TRANSLATE("Manage labels" B_UTF8_ELLIPSIS),
new BMessage(M_MANAGE_LABELS)));
if (currentLabel.Length() == 0)
menuItem->SetEnabled(false);
else {
menuItem = fLabelMenu->FindItem(currentLabel);
if (menuItem != NULL)
menuItem->SetMarked(true);
}
} else
fLabelMenu->SetEnabled(false);
}
void
TMailWindow::MenusEnded()
{
for (int32 i = fLabelMenu->CountItems(); i > 0; --i)
delete fLabelMenu->RemoveItem(i - 1);
BWindow::MenusEnded();
}
void
TMailWindow::MessageReceived(BMessage* msg)
{
bool wasReadMsg = false;
switch (msg->what) {
case B_MAIL_BODY_FETCHED:
{
status_t status = msg->FindInt32("status");
if (status != B_OK) {
fprintf(stderr, "Body could not be fetched: %s\n", strerror(status));
PostMessage(B_QUIT_REQUESTED);
break;
}
entry_ref ref;
if (msg->FindRef("ref", &ref) != B_OK)
break;
if (ref != *fRef)
break;
OpenMessage(&ref, _CurrentCharacterSet());
break;
}
case FIELD_CHANGED:
{
int32 prevState = fFieldState;
int32 fieldMask = msg->FindInt32("bitmask");
void* source;
if (msg->FindPointer("source", &source) == B_OK) {
int32 length;
if (fieldMask == FIELD_BODY)
length = ((TTextView*)source)->TextLength();
else
length = ((AddressTextControl*)source)->TextLength();
if (length)
fFieldState |= fieldMask;
else
fFieldState &= ~fieldMask;
}
if (prevState != fFieldState || !fChanged) {
fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, fFieldState);
fToolBar->SetActionEnabled(M_PRINT, fFieldState);
fToolBar->SetActionEnabled(M_SEND_NOW, (fFieldState & FIELD_TO)
|| (fFieldState & FIELD_BCC));
}
fChanged = true;
if (!fIncoming && (fieldMask & FIELD_SUBJECT) != 0) {
if (fHeaderView->IsSubjectEmpty())
SetTitle(B_TRANSLATE_SYSTEM_NAME("Mail"));
else
SetTitle(fHeaderView->Subject());
}
break;
}
case LIST_INVOKED:
PostMessage(msg, fEnclosuresView);
break;
case CHANGE_FONT:
PostMessage(msg, fContentView);
break;
case M_NEW:
{
BMessage message(M_NEW);
message.AddInt32("type", msg->what);
be_app->PostMessage(&message);
break;
}
case M_SPAM_BUTTON:
{
uint32 buttons;
if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK
&& buttons == B_SECONDARY_MOUSE_BUTTON) {
BPopUpMenu menu("Spam Actions", false, false);
for (int i = 0; i < 4; i++)
menu.AddItem(new BMenuItem(kSpamMenuItemTextArray[i],
new BMessage(M_TRAIN_SPAM_AND_DELETE + i)));
BPoint where;
msg->FindPoint("where", &where);
BMenuItem* item;
if ((item = menu.Go(where, false, false)) != NULL)
PostMessage(item->Message());
break;
} else {
PostMessage(new BMessage(M_TRAIN_SPAM_AND_DELETE));
}
break;
}
case M_TRAIN_SPAM_AND_DELETE:
PostMessage(M_DELETE_NEXT);
case M_TRAIN_SPAM:
TrainMessageAs("Spam");
break;
case M_UNTRAIN:
TrainMessageAs("Uncertain");
break;
case M_TRAIN_GENUINE:
TrainMessageAs("Genuine");
break;
case M_REPLY:
{
uint32 buttons;
if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK
&& buttons == B_SECONDARY_MOUSE_BUTTON) {
BPopUpMenu menu("Reply To", false, false);
menu.AddItem(new BMenuItem(B_TRANSLATE("Reply"),
new BMessage(M_REPLY)));
menu.AddItem(new BMenuItem(B_TRANSLATE("Reply to sender"),
new BMessage(M_REPLY_TO_SENDER)));
menu.AddItem(new BMenuItem(B_TRANSLATE("Reply to all"),
new BMessage(M_REPLY_ALL)));
BPoint where;
msg->FindPoint("where", &where);
BMenuItem* item;
if ((item = menu.Go(where, false, false)) != NULL) {
item->SetTarget(this);
PostMessage(item->Message());
}
break;
}
}
case M_FORWARD:
{
uint32 buttons;
if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK
&& buttons == B_SECONDARY_MOUSE_BUTTON) {
BPopUpMenu menu("Forward", false, false);
menu.AddItem(new BMenuItem(B_TRANSLATE("Forward"),
new BMessage(M_FORWARD)));
menu.AddItem(new BMenuItem(
B_TRANSLATE("Forward without attachments"),
new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS)));
BPoint where;
msg->FindPoint("where", &where);
BMenuItem* item;
if ((item = menu.Go(where, false, false)) != NULL) {
item->SetTarget(this);
PostMessage(item->Message());
}
break;
}
}
case M_REPLY_ALL:
case M_REPLY_TO_SENDER:
case M_FORWARD_WITHOUT_ATTACHMENTS:
case M_RESEND:
case M_COPY_TO_NEW:
{
BMessage message(M_NEW);
message.AddRef("ref", fRef);
message.AddPointer("window", this);
message.AddInt32("type", msg->what);
be_app->PostMessage(&message);
break;
}
case M_DELETE:
case M_DELETE_PREV:
case M_DELETE_NEXT:
{
if (msg->what == M_DELETE_NEXT && (modifiers() & B_SHIFT_KEY) != 0)
msg->what = M_DELETE_PREV;
bool foundRef = false;
entry_ref nextRef;
if ((msg->what == M_DELETE_PREV || msg->what == M_DELETE_NEXT)
&& fRef != NULL) {
nextRef = *fRef;
foundRef = GetTrackerWindowFile(&nextRef,
msg->what == M_DELETE_NEXT);
}
if (fIncoming) {
read_flags flag = (fAutoMarkRead == true) ? B_READ : B_SEEN;
MarkMessageRead(fRef, flag);
}
if (!fTrackerMessenger.IsValid() || !fIncoming) {
if (fDraft || fIncoming) {
BMessenger tracker("application/x-vnd.Be-TRAK");
if (tracker.IsValid()) {
BMessage msg('Ttrs');
msg.AddRef("refs", fRef);
tracker.SendMessage(&msg);
} else {
BAlert* alert = new BAlert("",
B_TRANSLATE("Need Tracker to move items to trash"),
B_TRANSLATE("Sorry"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
}
} else {
BMessage delmsg(B_DELETE_PROPERTY);
BMessage entryspec('sref');
entryspec.AddRef("refs", fRef);
entryspec.AddString("property", "Entry");
delmsg.AddSpecifier(&entryspec);
fTrackerMessenger.SendMessage(&delmsg);
}
if (foundRef) {
TMailWindow* window
= static_cast<TMailApp*>(be_app)->FindWindow(nextRef);
if (window == NULL)
OpenMessage(&nextRef, _CurrentCharacterSet());
else
window->Activate();
SetTrackerSelectionToCurrent();
if (window == NULL)
break;
}
fSent = true;
BMessage msg(B_CLOSE_REQUESTED);
PostMessage(&msg);
break;
}
case M_CLOSE_READ:
{
BMessage message(B_CLOSE_REQUESTED);
message.AddString("status", "Read");
PostMessage(&message);
break;
}
case M_CLOSE_SAVED:
{
BMessage message(B_CLOSE_REQUESTED);
message.AddString("status", "Saved");
PostMessage(&message);
break;
}
case kMsgQuitAndKeepAllStatus:
{
be_app->PostMessage(msg);
break;
}
case kMsgCloseAndKeepAllStatus:
fKeepStatusOnClose = true;
PostMessage(B_CLOSE_REQUESTED);
break;
case M_REMOVE_LABEL:
{
BNode node(fRef);
if (node.InitCheck() == B_NO_ERROR) {
fLabel = "";
node.RemoveAttr(B_MAIL_ATTR_LABEL);
BMenuItem* item = fLabelMenu->FindMarked();
if (item != NULL)
item->SetMarked(false);
item = fLabelMenu->FindItem(B_TRANSLATE("Remove label"));
if (item != NULL)
item->SetEnabled(false);
item = fQueryMenu->FindItem(kSameLabelItem);
if (item != NULL)
item->SetEnabled(false);
}
break;
}
case M_SET_LABEL:
{
const char* label = msg->FindString("label");
if (label != NULL) {
BNode node(fRef);
if (node.InitCheck() == B_NO_ERROR) {
fLabel = label;
node.RemoveAttr(B_MAIL_ATTR_LABEL);
WriteAttrString(&node, B_MAIL_ATTR_LABEL, label);
BMenuItem* item = fLabelMenu->FindMarked();
if (item != NULL)
item->SetMarked(false);
item = fLabelMenu->FindItem(label);
if (item != NULL) {
item->SetMarked(true);
item = fLabelMenu->FindItem(B_TRANSLATE("Remove label"));
if (item != NULL)
item->SetEnabled(true);
item = fQueryMenu->FindItem(kSameLabelItem);
if (item != NULL)
item->SetEnabled(true);
}
}
}
break;
}
case M_MANAGE_LABELS:
{
BPath path;
find_directory(B_USER_SETTINGS_DIRECTORY, &path);
path.Append("Mail/labels");
BEntry entry(path.Path());
if (!entry.Exists())
create_directory(path.Path(), 0777);
BEntry folderEntry;
if (folderEntry.SetTo(path.Path()) == B_OK
&& folderEntry.Exists()) {
BMessage openFolderCommand(B_REFS_RECEIVED);
BMessenger tracker("application/x-vnd.Be-TRAK");
entry_ref ref;
folderEntry.GetRef(&ref);
openFolderCommand.AddRef("refs", &ref);
tracker.SendMessage(&openFolderCommand);
}
break;
}
case M_NEW_LABEL:
new TLabelWindow(Frame(), this);
break;
case M_HEADER:
{
bool showHeader = !fHeader->IsMarked();
fHeader->SetMarked(showHeader);
BMessage message(M_HEADER);
message.AddBool("header", showHeader);
PostMessage(&message, fContentView->TextView());
break;
}
case M_RAW:
{
bool raw = !(fRaw->IsMarked());
fRaw->SetMarked(raw);
BMessage message(M_RAW);
message.AddBool("raw", raw);
PostMessage(&message, fContentView->TextView());
break;
}
case M_SEND_NOW:
case M_SAVE_AS_DRAFT:
Send(msg->what == M_SEND_NOW);
break;
case M_SAVE:
{
const char* address;
const char* name;
if (msg->FindString("address", (const char**)&address) != B_OK)
break;
if (msg->FindString("name", (const char**)&name) != B_OK)
break;
BVolumeRoster volumeRoster;
BVolume volume;
BQuery query;
BEntry entry;
bool foundEntry = false;
char* arg = (char*)malloc(strlen("META:email=")
+ strlen(address) + 1);
sprintf(arg, "META:email=%s", address);
while (volumeRoster.GetNextVolume(&volume) == B_NO_ERROR) {
if (!volume.KnowsQuery())
continue;
query.SetVolume(&volume);
query.SetPredicate(arg);
query.Fetch();
if (query.GetNextEntry(&entry) == B_NO_ERROR) {
BMessenger tracker("application/x-vnd.Be-TRAK");
if (tracker.IsValid()) {
entry_ref ref;
entry.GetRef(&ref);
BMessage open(B_REFS_RECEIVED);
open.AddRef("refs", &ref);
tracker.SendMessage(&open);
foundEntry = true;
break;
}
}
query.Clear();
}
if (!foundEntry) {
_CreateNewPerson(address, name);
}
free(arg);
break;
}
case M_READ_POS:
PreserveReadingPos(false);
break;
case M_PRINT_SETUP:
PrintSetup();
break;
case M_PRINT:
Print();
break;
case M_SELECT:
break;
case M_FIND:
FindWindow::Find(this);
break;
case M_FIND_AGAIN:
FindWindow::FindAgain(this);
break;
case M_ADD_QUOTE_LEVEL:
case M_SUB_QUOTE_LEVEL:
PostMessage(msg->what, fContentView);
break;
case M_RANDOM_SIG:
{
BList sigList;
BMessage *message;
BVolume volume;
BVolumeRoster().GetBootVolume(&volume);
BQuery query;
query.SetVolume(&volume);
char predicate[128];
sprintf(predicate, "%s = *", INDEX_SIGNATURE);
query.SetPredicate(predicate);
query.Fetch();
BEntry entry;
while (query.GetNextEntry(&entry) == B_NO_ERROR) {
BFile file(&entry, O_RDONLY);
if (file.InitCheck() == B_NO_ERROR) {
entry_ref ref;
entry.GetRef(&ref);
message = new BMessage(M_SIGNATURE);
message->AddRef("ref", &ref);
sigList.AddItem(message);
}
}
if (sigList.CountItems() > 0) {
srand(time(0));
PostMessage((BMessage*)sigList.ItemAt(rand()
% sigList.CountItems()));
for (int32 i = 0; (message = (BMessage*)sigList.ItemAt(i))
!= NULL; i++)
delete message;
}
break;
}
case M_SIGNATURE:
{
BMessage message(*msg);
PostMessage(&message, fContentView);
fSigAdded = true;
break;
}
case M_SIG_MENU:
{
TMenu* menu;
BMenuItem* item;
menu = new TMenu("Add Signature", INDEX_SIGNATURE, M_SIGNATURE,
true);
BPoint where;
if (msg->FindPoint("where", &where) != B_OK) {
BRect rect;
BButton* button = fToolBar->FindButton(M_SIG_MENU);
if (button != NULL)
rect = button->Frame();
else
rect = fToolBar->Bounds();
where = button->ConvertToScreen(BPoint(
((rect.right - rect.left) / 2) - 16,
(rect.bottom - rect.top) / 2));
}
if ((item = menu->Go(where, false, true)) != NULL) {
item->SetTarget(this);
(dynamic_cast<BInvoker*>(item))->Invoke();
}
delete menu;
break;
}
case M_ADD:
if (!fPanel) {
BMessenger me(this);
BMessage msg(REFS_RECEIVED);
fPanel = new BFilePanel(B_OPEN_PANEL, &me, &fOpenFolder, false,
true, &msg);
} else if (!fPanel->Window()->IsHidden()) {
fPanel->Window()->Activate();
}
if (fPanel->Window()->IsHidden())
fPanel->Window()->Show();
break;
case M_REMOVE:
PostMessage(msg->what, fEnclosuresView);
break;
case CHARSET_CHOICE_MADE:
{
int32 charSet;
if (msg->FindInt32("charset", &charSet) != B_OK)
break;
BMessage update(FIELD_CHANGED);
update.AddInt32("bitmask", 0);
PostMessage(&update);
if (fIncoming && !fResending) {
entry_ref fileRef = *fRef;
OpenMessage(&fileRef, charSet);
}
break;
}
case B_SIMPLE_DATA:
case REFS_RECEIVED:
AddEnclosure(msg);
break;
case M_UNREAD:
MarkMessageRead(fRef, B_SEEN);
_UpdateReadButton();
PostMessage(M_NEXTMSG);
break;
case M_READ:
wasReadMsg = true;
_UpdateReadButton();
msg->what = M_NEXTMSG;
case M_PREVMSG:
case M_NEXTMSG:
{
if (fRef == NULL)
break;
bool keepStatus;
if (msg->FindBool("keepStatus", &keepStatus) != B_OK)
keepStatus = false;
if (modifiers() & B_SHIFT_KEY)
keepStatus = true;
entry_ref orgRef = *fRef;
entry_ref nextRef = *fRef;
if (GetTrackerWindowFile(&nextRef, (msg->what == M_NEXTMSG))) {
TMailWindow* window = static_cast<TMailApp*>(be_app)
->FindWindow(nextRef);
if (window == NULL) {
BNode node(fRef);
read_flags currentFlag;
if (!keepStatus) {
if (read_read_attr(node, currentFlag) != B_OK)
currentFlag = B_UNREAD;
if (fAutoMarkRead == true)
MarkMessageRead(fRef, B_READ);
else if (currentFlag != B_READ && !wasReadMsg)
MarkMessageRead(fRef, B_SEEN);
}
OpenMessage(&nextRef, _CurrentCharacterSet());
} else {
window->Activate();
PostMessage(B_CLOSE_REQUESTED);
}
SetTrackerSelectionToCurrent();
} else {
if (wasReadMsg)
PostMessage(B_CLOSE_REQUESTED);
beep();
}
if (wasReadMsg)
MarkMessageRead(&orgRef, B_READ);
break;
}
case M_SAVE_POSITION:
if (fRef != NULL)
SaveTrackerPosition(fRef);
break;
case RESET_BUTTONS:
fChanged = false;
fFieldState = 0;
if (!fHeaderView->IsToEmpty())
fFieldState |= FIELD_TO;
if (!fHeaderView->IsSubjectEmpty())
fFieldState |= FIELD_SUBJECT;
if (!fHeaderView->IsCcEmpty())
fFieldState |= FIELD_CC;
if (!fHeaderView->IsBccEmpty())
fFieldState |= FIELD_BCC;
if (fContentView->TextView()->TextLength() != 0)
fFieldState |= FIELD_BODY;
fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false);
fToolBar->SetActionEnabled(M_PRINT, fFieldState);
fToolBar->SetActionEnabled(M_SEND_NOW, (fFieldState & FIELD_TO)
|| (fFieldState & FIELD_BCC));
break;
case M_CHECK_SPELLING:
if (gDictCount == 0)
snooze (1500000);
if (!gDictCount) {
beep();
BString text(B_TRANSLATE("%appname% couldn't find its dictionary."));
text.ReplaceFirst("%appname%", B_TRANSLATE_SYSTEM_NAME("Mail"));
BAlert* alert = new BAlert("", text,
B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
B_OFFSET_SPACING, B_STOP_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
} else {
fSpelling->SetMarked(!fSpelling->IsMarked());
fContentView->TextView()->EnableSpellCheck(
fSpelling->IsMarked());
}
break;
case M_QUERY_LABEL:
{
if (fLabel != "")
_LaunchQuery(kSameLabelItem, B_MAIL_ATTR_LABEL, fLabel);
break;
}
case M_QUERY_RECIPIENT:
{
BString searchText(fHeaderView->To());
if (searchText != "") {
_LaunchQuery(kSameRecipientItem, B_MAIL_ATTR_TO,
searchText);
}
break;
}
case M_QUERY_SENDER:
{
BString searchText(fHeaderView->From());
if (searchText != "") {
_LaunchQuery(kSameSenderItem, B_MAIL_ATTR_FROM,
searchText);
}
break;
}
case M_QUERY_SUBJECT:
{
BString searchText(fHeaderView->Subject());
BNode node(fRef);
if (node.InitCheck() == B_OK)
node.ReadAttrString(B_MAIL_ATTR_THREAD, &searchText);
if (searchText != "") {
_LaunchQuery(kSameSubjectItem, B_MAIL_ATTR_SUBJECT,
searchText);
}
break;
}
case M_EDIT_QUERIES:
{
BPath path;
if (_GetQueryPath(&path) < B_OK)
break;
BEntry entry(path.Path());
bool showAlert = false;
if (!entry.Exists()) {
showAlert = true;
create_directory(path.Path(), 0777);
}
BEntry folderEntry;
if (folderEntry.SetTo(path.Path()) == B_OK
&& folderEntry.Exists()) {
BMessage openFolderCommand(B_REFS_RECEIVED);
BMessenger tracker("application/x-vnd.Be-TRAK");
entry_ref ref;
folderEntry.GetRef(&ref);
openFolderCommand.AddRef("refs", &ref);
tracker.SendMessage(&openFolderCommand);
}
if (showAlert) {
snooze(250000);
BAlert* alert = new BAlert(B_TRANSLATE("helpful message"),
B_TRANSLATE("Put your favorite e-mail queries and query "
"templates in this folder."), B_TRANSLATE("OK"), NULL, NULL,
B_WIDTH_AS_USUAL, B_IDEA_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go(NULL);
}
break;
}
case B_PATH_MONITOR:
_RebuildQueryMenu();
break;
default:
BWindow::MessageReceived(msg);
}
}
void
TMailWindow::AddEnclosure(BMessage* msg)
{
if (fEnclosuresView == NULL && !fIncoming) {
fEnclosuresView = new TEnclosuresView;
AddChild(fEnclosuresView, fContentView);
}
if (fEnclosuresView == NULL)
return;
if (msg && msg->HasRef("refs")) {
PostMessage(msg, fEnclosuresView);
fChanged = true;
BEntry entry;
entry_ref ref;
msg->FindRef("refs", &ref);
entry.SetTo(&ref);
entry.GetParent(&entry);
entry.GetRef(&fOpenFolder);
}
}
bool
TMailWindow::QuitRequested()
{
int32 result;
if ((!fIncoming || (fIncoming && fResending)) && fChanged && !fSent
&& (!fHeaderView->IsToEmpty()
|| !fHeaderView->IsSubjectEmpty()
|| !fHeaderView->IsCcEmpty()
|| !fHeaderView->IsBccEmpty()
|| (fContentView->TextView() != NULL
&& strlen(fContentView->TextView()->Text()))
|| (fEnclosuresView != NULL
&& fEnclosuresView->fList->CountItems()))) {
if (fResending) {
BAlert* alert = new BAlert("", B_TRANSLATE(
"Send this message before closing?"),
B_TRANSLATE("Cancel"),
B_TRANSLATE("Don't send"),
B_TRANSLATE("Send"),
B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->SetShortcut(1, 'd');
alert->SetShortcut(2, 's');
result = alert->Go();
switch (result) {
case 0:
return false;
case 1:
break;
case 2:
Send(true);
break;
}
} else {
BAlert* alert = new BAlert("",
B_TRANSLATE("Save this message as a draft before closing?"),
B_TRANSLATE("Cancel"),
B_TRANSLATE("Don't save"),
B_TRANSLATE("Save"),
B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->SetShortcut(1, 'd');
alert->SetShortcut(2, 's');
result = alert->Go();
switch (result) {
case 0:
return false;
case 1:
break;
case 2:
Send(false);
break;
}
}
}
BMessage message(WINDOW_CLOSED);
message.AddInt32("kind", MAIL_WINDOW);
message.AddPointer("window", this);
be_app->PostMessage(&message);
if (CurrentMessage() && CurrentMessage()->HasString("status")) {
if (!CurrentMessage()->HasString("same")) {
const char* status = CurrentMessage()->FindString("status");
if (status != NULL) {
BNode node(fRef);
if (node.InitCheck() == B_NO_ERROR) {
node.RemoveAttr(B_MAIL_ATTR_STATUS);
WriteAttrString(&node, B_MAIL_ATTR_STATUS, status);
}
}
}
} else if (fRef != NULL && !fKeepStatusOnClose) {
if (fAutoMarkRead == true)
MarkMessageRead(fRef, B_READ);
else {
BNode node(fRef);
read_flags currentFlag;
if (read_read_attr(node, currentFlag) != B_OK)
currentFlag = B_UNREAD;
if (currentFlag == B_UNREAD)
MarkMessageRead(fRef, B_SEEN);
}
}
BPrivate::BPathMonitor::StopWatching(BMessenger(this, this));
return true;
}
void
TMailWindow::Show()
{
if (Lock()) {
if (!fResending && (fIncoming || fReplying)) {
fContentView->TextView()->MakeFocus(true);
} else {
fHeaderView->ToControl()->MakeFocus(true);
fHeaderView->ToControl()->SelectAll();
}
Unlock();
}
BWindow::Show();
}
void
TMailWindow::Zoom(BPoint , float , float )
{
float height;
float width;
BRect rect = Frame();
width = 80 * fApp->ContentFont().StringWidth("M")
+ (rect.Width() - fContentView->TextView()->Bounds().Width() + 6);
BScreen screen(this);
BRect screenFrame = screen.Frame();
if (width > (screenFrame.Width() - 8))
width = screenFrame.Width() - 8;
height = max_c(fContentView->TextView()->CountLines(), 20)
* fContentView->TextView()->LineHeight(0)
+ (rect.Height() - fContentView->TextView()->Bounds().Height());
if (height > (screenFrame.Height() - 29))
height = screenFrame.Height() - 29;
rect.right = rect.left + width;
rect.bottom = rect.top + height;
if (abs((int)(Frame().Width() - rect.Width())) < 5
&& abs((int)(Frame().Height() - rect.Height())) < 5) {
rect = fZoom;
} else {
fZoom = Frame();
screenFrame.InsetBy(6, 6);
if (rect.Width() > screenFrame.Width())
rect.right = rect.left + screenFrame.Width();
if (rect.Height() > screenFrame.Height())
rect.bottom = rect.top + screenFrame.Height();
if (rect.right > screenFrame.right) {
rect.left -= rect.right - screenFrame.right;
rect.right = screenFrame.right;
}
if (rect.bottom > screenFrame.bottom) {
rect.top -= rect.bottom - screenFrame.bottom;
rect.bottom = screenFrame.bottom;
}
if (rect.left < screenFrame.left) {
rect.right += screenFrame.left - rect.left;
rect.left = screenFrame.left;
}
if (rect.top < screenFrame.top) {
rect.bottom += screenFrame.top - rect.top;
rect.top = screenFrame.top;
}
}
ResizeTo(rect.Width(), rect.Height());
MoveTo(rect.LeftTop());
}
void
TMailWindow::WindowActivated(bool status)
{
if (status) {
BAutolock locker(sWindowListLock);
sWindowList.RemoveItem(this);
sWindowList.AddItem(this, 0);
}
}
void
TMailWindow::Forward(entry_ref* ref, TMailWindow* window,
bool includeAttachments)
{
BEmailMessage* mail = window->Mail();
if (mail == NULL)
return;
uint32 useAccountFrom = fApp->UseAccountFrom();
fMail = mail->ForwardMessage(useAccountFrom == ACCOUNT_FROM_MAIL,
includeAttachments);
BFile file(ref, O_RDONLY);
if (file.InitCheck() < B_NO_ERROR)
return;
fHeaderView->SetSubject(fMail->Subject());
if (useAccountFrom == ACCOUNT_FROM_MAIL)
fHeaderView->SetAccount(fMail->Account());
if (fMail->CountComponents() > 1) {
AddEnclosure(NULL);
if (fEnclosuresView)
fEnclosuresView->AddEnclosuresFromMail(fMail);
}
fContentView->TextView()->LoadMessage(fMail, false, NULL);
fChanged = false;
fFieldState = 0;
}
void
TMailWindow::Print()
{
BPrintJob print(Title());
if (!fApp->HasPrintSettings()) {
if (print.Settings()) {
fApp->SetPrintSettings(print.Settings());
} else {
PrintSetup();
if (!fApp->HasPrintSettings())
return;
}
}
print.SetSettings(new BMessage(fApp->PrintSettings()));
if (print.ConfigJob() == B_OK) {
int32 curPage = 1;
int32 lastLine = 0;
BTextView header_view(print.PrintableRect(), "header",
print.PrintableRect().OffsetByCopy(BPoint(
-print.PrintableRect().left, -print.PrintableRect().top)),
B_FOLLOW_ALL_SIDES);
#define add_header_field(label, field) { \
\
header_view.Insert(label); \
header_view.Insert(" "); \
\
header_view.Insert(field); \
header_view.Insert("\n"); \
}
add_header_field("Subject:", fHeaderView->Subject());
add_header_field("To:", fHeaderView->To());
if (!fHeaderView->IsCcEmpty())
add_header_field(B_TRANSLATE("Cc:"), fHeaderView->Cc());
if (!fHeaderView->IsDateEmpty())
header_view.Insert(fHeaderView->Date());
int32 maxLine = fContentView->TextView()->CountLines();
BRect pageRect = print.PrintableRect();
BRect curPageRect = pageRect;
print.BeginJob();
float header_height = header_view.TextHeight(0,
header_view.CountLines());
BRect rect(0, 0, pageRect.Width(), header_height);
BBitmap bmap(rect, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
bmap.Lock();
bmap.AddChild(&header_view);
print.DrawView(&header_view, rect, BPoint(0.0, 0.0));
HorizontalLine line(BRect(0, 0, pageRect.right, 0));
bmap.AddChild(&line);
print.DrawView(&line, line.Bounds(), BPoint(0, header_height + 1));
bmap.Unlock();
header_height += 5;
do {
int32 lineOffset = fContentView->TextView()->OffsetAt(lastLine);
curPageRect.OffsetTo(0,
fContentView->TextView()->PointAt(lineOffset).y);
int32 fromLine = lastLine;
lastLine = fContentView->TextView()->LineAt(
BPoint(0.0, curPageRect.bottom - ((curPage == 1)
? header_height : 0)));
float curPageHeight = fContentView->TextView()->TextHeight(
fromLine, lastLine) + (curPage == 1 ? header_height : 0);
if (curPageHeight > pageRect.Height()) {
curPageHeight = fContentView->TextView()->TextHeight(
fromLine, --lastLine) + (curPage == 1 ? header_height : 0);
}
curPageRect.bottom = curPageRect.top + curPageHeight - 1.0;
if (curPage >= print.FirstPage() && curPage <= print.LastPage()) {
print.DrawView(fContentView->TextView(), curPageRect,
BPoint(0.0, curPage == 1 ? header_height : 0.0));
print.SpoolPage();
}
curPageRect = pageRect;
lastLine++;
curPage++;
} while (print.CanContinue() && lastLine < maxLine);
print.CommitJob();
bmap.RemoveChild(&header_view);
bmap.RemoveChild(&line);
}
}
void
TMailWindow::PrintSetup()
{
BPrintJob printJob("mail_print");
if (fApp->HasPrintSettings()) {
BMessage printSettings = fApp->PrintSettings();
printJob.SetSettings(new BMessage(printSettings));
}
if (printJob.ConfigPage() == B_OK)
fApp->SetPrintSettings(printJob.Settings());
}
void
TMailWindow::SetTo(const char* mailTo, const char* subject, const char* ccTo,
const char* bccTo, const BString* body, BMessage* enclosures)
{
Lock();
if (mailTo != NULL && mailTo[0])
fHeaderView->SetTo(mailTo);
if (subject != NULL && subject[0])
fHeaderView->SetSubject(subject);
if (ccTo != NULL && ccTo[0])
fHeaderView->SetCc(ccTo);
if (bccTo != NULL && bccTo[0])
fHeaderView->SetBcc(bccTo);
if (body != NULL && body->Length()) {
fContentView->TextView()->SetText(body->String(), body->Length());
fContentView->TextView()->GoToLine(0);
}
if (enclosures && enclosures->HasRef("refs"))
AddEnclosure(enclosures);
Unlock();
}
void
TMailWindow::CopyMessage(entry_ref* ref, TMailWindow* src)
{
BNode file(ref);
if (file.InitCheck() == B_OK) {
BString string;
if (file.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK)
fHeaderView->SetTo(string);
if (file.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK)
fHeaderView->SetSubject(string);
if (file.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK)
fHeaderView->SetCc(string);
}
TTextView* text = src->fContentView->TextView();
text_run_array* style = text->RunArray(0, text->TextLength());
fContentView->TextView()->SetText(text->Text(), text->TextLength(), style);
free(style);
}
void
TMailWindow::Reply(entry_ref* ref, TMailWindow* window, uint32 type)
{
fRepliedMail = *ref;
SetOriginatingWindow(window);
BEmailMessage* mail = window->Mail();
if (mail == NULL)
return;
if (type == M_REPLY_ALL)
type = B_MAIL_REPLY_TO_ALL;
else if (type == M_REPLY_TO_SENDER)
type = B_MAIL_REPLY_TO_SENDER;
else
type = B_MAIL_REPLY_TO;
uint32 useAccountFrom = fApp->UseAccountFrom();
fMail = mail->ReplyMessage(mail_reply_to_mode(type),
useAccountFrom == ACCOUNT_FROM_MAIL, QUOTE);
fHeaderView->SetTo(fMail->To());
fHeaderView->SetCc(fMail->CC());
fHeaderView->SetSubject(fMail->Subject());
int32 accountID;
BFile file(window->fRef, B_READ_ONLY);
if (file.ReadAttr("MAIL:reply_with", B_INT32_TYPE, 0, &accountID,
sizeof(int32)) != B_OK)
accountID = -1;
if ((useAccountFrom == ACCOUNT_FROM_MAIL) || (accountID > -1)) {
if (useAccountFrom == ACCOUNT_FROM_MAIL)
fHeaderView->SetAccount(fMail->Account());
else
fHeaderView->SetAccount(accountID);
}
BString preamble = fApp->ReplyPreamble();
BString name;
mail->GetName(&name);
if (name.Length() <= 0)
name = B_TRANSLATE("(Name unavailable)");
BString address(mail->From());
if (address.Length() <= 0)
address = B_TRANSLATE("(Address unavailable)");
BString date(mail->HeaderField("Date"));
if (date.Length() <= 0)
date = B_TRANSLATE("(Date unavailable)");
preamble.ReplaceAll("%n", name);
preamble.ReplaceAll("%e", address);
preamble.ReplaceAll("%d", date);
preamble.ReplaceAll("\\n", "\n");
int32 finish, start;
window->fContentView->TextView()->GetSelection(&start, &finish);
if (start != finish) {
char* text = (char*)malloc(finish - start + 1);
if (text == NULL)
return;
window->fContentView->TextView()->GetText(start, finish - start, text);
if (text[strlen(text) - 1] != '\n') {
text[strlen(text)] = '\n';
finish++;
}
fContentView->TextView()->SetText(text, finish - start);
free(text);
finish = fContentView->TextView()->CountLines();
for (int32 loop = 0; loop < finish; loop++) {
fContentView->TextView()->GoToLine(loop);
fContentView->TextView()->Insert((const char*)QUOTE);
}
if (fApp->ColoredQuotes()) {
const BFont* font = fContentView->TextView()->Font();
int32 length = fContentView->TextView()->TextLength();
TextRunArray style(length / 8 + 8);
FillInQuoteTextRuns(fContentView->TextView(), NULL,
fContentView->TextView()->Text(), length, font, &style.Array(),
style.MaxEntries());
fContentView->TextView()->SetRunArray(0, length, &style.Array());
}
fContentView->TextView()->GoToLine(0);
if (preamble.Length() > 0)
fContentView->TextView()->Insert(preamble);
} else {
fContentView->TextView()->LoadMessage(mail, true, preamble);
}
fReplying = true;
}
status_t
TMailWindow::Send(bool now)
{
if (!now) {
status_t status = SaveAsDraft();
if (status != B_OK) {
beep();
BAlert* alert = new BAlert("", B_TRANSLATE("E-mail draft could "
"not be saved!"), B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
return status;
}
uint32 characterSetToUse = _CurrentCharacterSet();
mail_encoding encodingForBody = quoted_printable;
mail_encoding encodingForHeaders = quoted_printable;
if (characterSetToUse == B_SJIS_CONVERSION
|| characterSetToUse == B_EUC_CONVERSION)
encodingForBody = base64;
else if (characterSetToUse == B_JIS_CONVERSION
|| characterSetToUse == B_MAIL_US_ASCII_CONVERSION
|| characterSetToUse == B_ISO1_CONVERSION
|| characterSetToUse == B_EUC_KR_CONVERSION)
encodingForBody = eight_bit;
if (characterSetToUse == B_SJIS_CONVERSION
|| characterSetToUse == B_EUC_CONVERSION
|| characterSetToUse == B_JIS_CONVERSION
|| characterSetToUse == B_EUC_KR_CONVERSION)
encodingForHeaders = base64;
if (fContentView->TextView()->TextLength() > 0) {
int32 converterState = 0;
int32 originalLength;
BString tempString;
int32 tempStringLength;
char* tempStringPntr;
originalLength = fContentView->TextView()->TextLength();
tempStringLength = originalLength * 6;
tempStringPntr = tempString.LockBuffer (tempStringLength);
if (tempStringPntr != NULL && mail_convert_from_utf8(characterSetToUse,
fContentView->TextView()->Text(), &originalLength,
tempStringPntr, &tempStringLength, &converterState,
0x1A ) == B_OK) {
int i;
bool has8Bit = false;
for (i = 0; i < tempStringLength; i++) {
if (tempString[i] == 0 || (tempString[i] & 0x80)) {
has8Bit = true;
break;
}
}
if (!has8Bit)
encodingForBody = seven_bit;
tempString.UnlockBuffer (tempStringLength);
if (fApp->WarnAboutUnencodableCharacters()) {
int32 offset = 0;
int count = 0;
while (offset >= 0) {
offset = tempString.FindFirst (0x1A, offset);
if (offset >= 0) {
count++;
offset++;
}
}
if (count > 0) {
int32 userAnswer;
BString messageString;
BString countString;
countString << count;
messageString << B_TRANSLATE("Your main text contains %ld"
" unencodable characters. Perhaps a different "
"character set would work better? Hit Send to send it "
"anyway "
"(a substitute character will be used in place of "
"the unencodable ones), or choose Cancel to go back "
"and try fixing it up.");
messageString.ReplaceFirst("%ld", countString);
BAlert* alert = new BAlert("Question", messageString.String(),
B_TRANSLATE("Send"),
B_TRANSLATE("Cancel"),
NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
B_WARNING_ALERT);
alert->SetShortcut(1, B_ESCAPE);
userAnswer = alert->Go();
if (userAnswer == 1) {
return -1;
}
}
}
}
}
Hide();
status_t result;
if (fResending) {
BFile file(fRef, O_RDONLY);
result = file.InitCheck();
if (result == B_OK) {
BEmailMessage mail(&file);
mail.SetTo(fHeaderView->To(), characterSetToUse,
encodingForHeaders);
if (fHeaderView->AccountID() != ~0L)
mail.SendViaAccount(fHeaderView->AccountID());
result = mail.Send(now);
}
} else {
if (fMail == NULL)
fMail = new BEmailMessage;
fMail->SetTo(fHeaderView->To(), characterSetToUse, encodingForHeaders);
fMail->SetSubject(fHeaderView->Subject(), characterSetToUse,
encodingForHeaders);
fMail->SetCC(fHeaderView->Cc(), characterSetToUse, encodingForHeaders);
fMail->SetBCC(fHeaderView->Bcc());
{
version_info info;
memset(&info, 0, sizeof(version_info));
app_info appInfo;
if (be_app->GetAppInfo(&appInfo) == B_OK) {
BFile file(&appInfo.ref, B_READ_ONLY);
if (file.InitCheck() == B_OK) {
BAppFileInfo appFileInfo(&file);
if (appFileInfo.InitCheck() == B_OK)
appFileInfo.GetVersionInfo(&info, B_APP_VERSION_KIND);
}
}
char versionString[255];
sprintf(versionString,
"Mail/Haiku %" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32,
info.major, info.middle, info.minor);
fMail->SetHeaderField("X-Mailer", versionString);
}
fMail->SetBodyTextTo("");
fContentView->TextView()->AddAsContent(fMail, fApp->WrapMode(),
characterSetToUse, encodingForBody);
if (fEnclosuresView != NULL) {
TListItem* item;
int32 index = 0;
while ((item = (TListItem*)fEnclosuresView->fList->ItemAt(index++))
!= NULL) {
if (item->Component())
continue;
BEntry entry(item->Ref());
if (!entry.Exists())
continue;
fMail->Attach(item->Ref(), fApp->AttachAttributes());
}
}
if (fHeaderView->AccountID() != ~0L)
fMail->SendViaAccount(fHeaderView->AccountID());
result = fMail->Send(now);
if (fReplying) {
BNode node(&fRepliedMail);
if (node.InitCheck() >= B_OK) {
if (fOriginatingWindow) {
BMessage msg(M_SAVE_POSITION), reply;
fOriginatingWindow->SendMessage(&msg, &reply);
}
WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Replied");
}
}
}
bool close = false;
BString errorMessage;
switch (result) {
case B_OK:
close = true;
fSent = true;
if (fDraft) {
BEntry entry(fRef);
entry.Remove();
}
break;
case B_MAIL_NO_DAEMON:
{
close = true;
fSent = true;
BAlert* alert = new BAlert("no daemon",
B_TRANSLATE("The mail_daemon is not running. The message is "
"queued and will be sent when the mail_daemon is started."),
B_TRANSLATE("Start now"), B_TRANSLATE("OK"));
alert->SetShortcut(1, B_ESCAPE);
int32 start = alert->Go();
if (start == 0) {
BMailDaemon daemon;
result = daemon.Launch();
if (result == B_OK) {
daemon.SendQueuedMail();
} else {
errorMessage
<< B_TRANSLATE("The mail_daemon could not be "
"started:\n\t")
<< strerror(result);
}
}
break;
}
default:
errorMessage << "An error occurred trying to send mail:\n\t"
<< strerror(result);
break;
}
if (result != B_NO_ERROR && result != B_MAIL_NO_DAEMON) {
beep();
BAlert* alert = new BAlert("", errorMessage.String(),
B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
if (close) {
PostMessage(B_QUIT_REQUESTED);
} else {
Show();
}
return result;
}
status_t
TMailWindow::SaveAsDraft()
{
BPath draftPath;
BDirectory dir;
BFile draft;
uint32 flags = 0;
if (fDraft) {
status_t status = draft.SetTo(fRef,
B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
if (status != B_OK)
return status;
} else {
status_t status = find_directory(B_USER_DIRECTORY, &draftPath);
if (status != B_OK)
return status;
draftPath.Append(kDraftPath);
status = dir.SetTo(draftPath.Path());
switch (status) {
case B_ENTRY_NOT_FOUND:
if ((status = dir.CreateDirectory(draftPath.Path(), &dir))
!= B_OK)
return status;
case B_OK:
{
char fileName[B_FILE_NAME_LENGTH];
if (fHeaderView->IsSubjectEmpty()) {
strlcpy(fileName, B_TRANSLATE("Untitled"),
sizeof(fileName));
} else {
strlcpy(fileName, fHeaderView->Subject(), sizeof(fileName));
}
uint32 originalLength = strlen(fileName);
for (char* bad = fileName; (bad = strchr(bad, '/')) != NULL;
++bad) {
*bad = '-';
}
for (char* bad = fileName; (bad = strchr(bad, '\\')) != NULL;
++bad) {
*bad = '-';
}
for (char* bad = fileName; (bad = strchr(bad, ':')) != NULL;
++bad) {
*bad = '-';
}
flags = B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS;
int32 i = 1;
do {
status = draft.SetTo(&dir, fileName, flags);
if (status == B_OK)
break;
char appendix[B_FILE_NAME_LENGTH];
sprintf(appendix, " %" B_PRId32, i++);
int32 pos = min_c(sizeof(fileName) - strlen(appendix),
originalLength);
sprintf(fileName + pos, "%s", appendix);
} while (status == B_FILE_EXISTS);
if (status != B_OK)
return status;
if (fRef == NULL)
fRef = new entry_ref;
BEntry entry(&dir, fileName);
entry.GetRef(fRef);
break;
}
default:
return status;
}
}
draft.Write(fContentView->TextView()->Text(),
fContentView->TextView()->TextLength());
WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->To());
WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->To());
WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->Subject());
if (!fHeaderView->IsCcEmpty())
WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->Cc());
if (!fHeaderView->IsBccEmpty())
WriteAttrString(&draft, B_MAIL_ATTR_BCC, fHeaderView->Bcc());
if (fHeaderView->AccountName() != NULL) {
WriteAttrString(&draft, B_MAIL_ATTR_ACCOUNT,
fHeaderView->AccountName());
}
BMenuItem* menuItem = fEncodingMenu->FindMarked();
if (menuItem != NULL)
WriteAttrString(&draft, "MAIL:encoding", menuItem->Label());
uint32 draftAttr = true;
draft.WriteAttr("MAIL:draft", B_INT32_TYPE, 0, &draftAttr, sizeof(uint32));
if (fEnclosuresView != NULL) {
TListItem* item;
BString pathStr;
for (int32 i = 0; (item = (TListItem*)fEnclosuresView->fList->ItemAt(i))
!= NULL; i++) {
if (i > 0)
pathStr.Append(":");
BEntry entry(item->Ref(), true);
if (!entry.Exists())
continue;
BPath path;
entry.GetPath(&path);
pathStr.Append(path.Path());
}
if (pathStr.Length())
draft.WriteAttrString("MAIL:attachments", &pathStr);
}
BNodeInfo info(&draft);
info.SetType(kDraftType);
fDraft = true;
fChanged = false;
fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false);
return B_OK;
}
status_t
TMailWindow::TrainMessageAs(const char* commandWord)
{
status_t errorCode = -1;
BEntry fileEntry;
BPath filePath;
BMessage replyMessage;
BMessage scriptingMessage;
team_id serverTeam;
if (fRef == NULL)
goto ErrorExit;
errorCode = fileEntry.SetTo(fRef, true);
if (errorCode != B_OK)
goto ErrorExit;
errorCode = fileEntry.GetPath(&filePath);
if (errorCode != B_OK)
goto ErrorExit;
fileEntry.Unset();
if (!fMessengerToSpamServer.IsValid()) {
if (!be_roster->IsRunning (kSpamServerSignature)) {
errorCode = be_roster->Launch (kSpamServerSignature);
if (errorCode != B_OK) {
BPath path;
entry_ref ref;
directory_which places[] = {B_SYSTEM_NONPACKAGED_BIN_DIRECTORY,
B_SYSTEM_BIN_DIRECTORY};
for (int32 i = 0; i < 2; i++) {
find_directory(places[i],&path);
path.Append("spamdbm");
if (!BEntry(path.Path()).Exists())
continue;
get_ref_for_path(path.Path(),&ref);
errorCode = be_roster->Launch(&ref);
if (errorCode == B_OK)
break;
}
if (errorCode != B_OK)
goto ErrorExit;
}
}
errorCode = B_SERVER_NOT_FOUND;
serverTeam = be_roster->TeamFor(kSpamServerSignature);
if (serverTeam < 0)
goto ErrorExit;
fMessengerToSpamServer = BMessenger (kSpamServerSignature, serverTeam,
&errorCode);
if (!fMessengerToSpamServer.IsValid())
goto ErrorExit;
}
scriptingMessage.MakeEmpty();
scriptingMessage.what = B_SET_PROPERTY;
scriptingMessage.AddSpecifier(commandWord);
errorCode = scriptingMessage.AddData("data", B_STRING_TYPE,
filePath.Path(), strlen(filePath.Path()) + 1, false);
if (errorCode != B_OK)
goto ErrorExit;
replyMessage.MakeEmpty();
errorCode = fMessengerToSpamServer.SendMessage(&scriptingMessage,
&replyMessage);
if (errorCode != B_OK
|| replyMessage.FindInt32("error", &errorCode) != B_OK
|| errorCode != B_OK)
goto ErrorExit;
SetTitleForMessage();
return B_OK;
ErrorExit:
beep();
char errorString[1500];
snprintf(errorString, sizeof(errorString), "Unable to train the message "
"file \"%s\" as %s. Possibly useful error code: %s (%" B_PRId32 ").",
filePath.Path(), commandWord, strerror(errorCode), errorCode);
BAlert* alert = new BAlert("", errorString, B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
return errorCode;
}
void
TMailWindow::SetTitleForMessage()
{
BString title = B_TRANSLATE_SYSTEM_NAME("Mail");
if (fIncoming) {
if (fMail->GetName(&title) == B_OK)
title << ": \"" << fMail->Subject() << "\"";
else
title = fMail->Subject();
if (fDownloading)
title.Prepend("Downloading: ");
if (fApp->ShowSpamGUI() && fRef != NULL) {
BString classification;
BNode node(fRef);
char numberString[30];
BString oldTitle(title);
float spamRatio;
if (node.InitCheck() != B_OK || node.ReadAttrString(
"MAIL:classification", &classification) != B_OK)
classification = "Unrated";
if (classification != "Spam" && classification != "Genuine") {
if (node.InitCheck() == B_OK && node.ReadAttr("MAIL:ratio_spam",
B_FLOAT_TYPE, 0, &spamRatio, sizeof(spamRatio))
== sizeof(spamRatio)) {
sprintf(numberString, "%.4f", spamRatio);
classification << " " << numberString;
}
}
title = "";
title << "[" << classification << "] " << oldTitle;
}
}
SetTitle(title);
}
status_t
TMailWindow::OpenMessage(const entry_ref* ref, uint32 characterSetForDecoding)
{
if (ref == NULL)
return B_ERROR;
delete fRef;
fRef = new entry_ref(*ref);
fPrevTrackerPositionSaved = false;
fNextTrackerPositionSaved = false;
fContentView->TextView()->StopLoad();
delete fMail;
fMail = NULL;
BFile file(fRef, B_READ_ONLY);
status_t err = file.InitCheck();
if (err != B_OK)
return err;
char mimeType[256];
BNodeInfo fileInfo(&file);
fileInfo.GetType(mimeType);
if (strcmp(mimeType, B_PARTIAL_MAIL_TYPE) == 0) {
BMessenger listener(this);
status_t status = BMailDaemon().FetchBody(*ref, &listener);
if (status != B_OK)
fprintf(stderr, "Could not fetch body: %s\n", strerror(status));
fileInfo.GetType(mimeType);
_SetDownloading(true);
} else
_SetDownloading(false);
if (strcmp(kDraftType, mimeType) == 0) {
BNode node(fRef);
off_t size;
BString string;
fMail = new BEmailMessage;
file.GetSize(&size);
fContentView->TextView()->SetText(&file, 0, size);
if (node.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK)
fHeaderView->SetTo(string);
if (node.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK)
fHeaderView->SetSubject(string);
if (node.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK)
fHeaderView->SetCc(string);
if (node.ReadAttrString(B_MAIL_ATTR_BCC, &string) == B_OK)
fHeaderView->SetBcc(string);
if (node.ReadAttrString(B_MAIL_ATTR_ACCOUNT, &string) == B_OK)
fHeaderView->SetAccount(string);
if (node.ReadAttrString("MAIL:encoding", &string) == B_OK) {
BMenuItem* encodingItem = fEncodingMenu->FindItem(string.String());
if (encodingItem != NULL)
encodingItem->SetMarked(true);
}
if (node.ReadAttrString("MAIL:attachments", &string) == B_OK) {
BMessage msg(REFS_RECEIVED);
entry_ref enc_ref;
BStringList list;
string.Split(":", false, list);
for (int32 i = 0; i < list.CountStrings(); i++) {
BEntry entry(list.StringAt(i), true);
if (entry.Exists()) {
entry.GetRef(&enc_ref);
msg.AddRef("refs", &enc_ref);
}
}
AddEnclosure(&msg);
}
PostMessage(M_READ_POS);
PostMessage(RESET_BUTTONS);
fIncoming = false;
fDraft = true;
} else {
fMail = new BEmailMessage(fRef, characterSetForDecoding);
fIncoming = true;
fHeaderView->SetFromMessage(fMail);
}
err = fMail->InitCheck();
if (err < B_OK) {
delete fMail;
fMail = NULL;
return err;
}
SetTitleForMessage();
if (fIncoming) {
BMenuItem* item;
while ((item = fSaveAddrMenu->RemoveItem((int32)0)) != NULL)
delete item;
BList addressList;
get_address_list(addressList, fMail->To(), extract_address);
get_address_list(addressList, fMail->CC(), extract_address);
get_address_list(addressList, fMail->From(), extract_address);
get_address_list(addressList, fMail->ReplyTo(), extract_address);
BList nameList;
get_address_list(nameList, fMail->To(), extract_address_name);
get_address_list(nameList, fMail->CC(), extract_address_name);
get_address_list(nameList, fMail->From(), extract_address_name);
get_address_list(nameList, fMail->ReplyTo(), extract_address_name);
BMessage* msg;
for (int32 i = addressList.CountItems(); i-- > 0;) {
char* address = (char*)addressList.RemoveItem((int32)0);
char* name = (char*)nameList.RemoveItem((int32)0);
int32 index = 0;
while ((item = fSaveAddrMenu->ItemAt(index)) != NULL) {
if (!strcmp(address, item->Label())) {
goto skip;
}
if (strcmp(address, item->Label()) < 0)
break;
index++;
}
msg = new BMessage(M_SAVE);
msg->AddString("address", address);
msg->AddString("name", name);
fSaveAddrMenu->AddItem(new BMenuItem(address, msg), index);
skip:
free(address);
free(name);
}
fContentView->TextView()->SetText("", (int32)0);
fContentView->TextView()->LoadMessage(fMail, false, NULL);
if (fApp->ShowToolBar())
_UpdateReadButton();
}
return B_OK;
}
TMailWindow*
TMailWindow::FrontmostWindow()
{
BAutolock locker(sWindowListLock);
if (sWindowList.CountItems() > 0)
return (TMailWindow*)sWindowList.ItemAt(0);
return NULL;
}
status_t
TMailWindow::_GetQueryPath(BPath* queryPath) const
{
status_t ret = find_directory(B_USER_DIRECTORY, queryPath);
if (ret == B_OK)
ret = queryPath->Append(kQueriesDirectory);
return ret;
}
void
TMailWindow::_RebuildQueryMenu(bool firstTime)
{
while (fQueryMenu->ItemAt(0)) {
BMenuItem* item = fQueryMenu->RemoveItem((int32)0);
delete item;
}
fQueryMenu->AddItem(new BMenuItem(kSameRecipientItem,
new BMessage(M_QUERY_RECIPIENT)));
fQueryMenu->AddItem(new BMenuItem(kSameSenderItem,
new BMessage(M_QUERY_SENDER)));
fQueryMenu->AddItem(new BMenuItem(kSameSubjectItem,
new BMessage(M_QUERY_SUBJECT)));
BMenuItem* item = new BMenuItem(kSameLabelItem, new BMessage(M_QUERY_LABEL));
fQueryMenu->AddItem(item);
item->SetEnabled(fLabel.Length() > 0 ? true : false);
BPath queryPath;
if (_GetQueryPath(&queryPath) < B_OK)
return;
BDirectory queryDir(queryPath.Path());
if (firstTime) {
BPrivate::BPathMonitor::StartWatching(queryPath.Path(),
B_WATCH_RECURSIVELY, BMessenger(this, this));
}
BEntry entry;
while (queryDir.GetNextEntry(&entry) == B_OK) {
char name[B_FILE_NAME_LENGTH + 1];
entry.GetName(name);
char* queryString = _BuildQueryString(&entry);
if (queryString == NULL)
continue;
QueryMenu* queryMenu = new QueryMenu(name, false);
queryMenu->SetTargetForItems(be_app);
queryMenu->SetPredicate(queryString);
fQueryMenu->AddItem(queryMenu);
free(queryString);
}
fQueryMenu->AddItem(new BSeparatorItem());
fQueryMenu->AddItem(new BMenuItem(B_TRANSLATE("Edit queries"
B_UTF8_ELLIPSIS),
new BMessage(M_EDIT_QUERIES), 'E', B_SHIFT_KEY));
}
char*
TMailWindow::_BuildQueryString(BEntry* entry) const
{
BNode node(entry);
if (node.InitCheck() != B_OK)
return NULL;
uint32 mode;
if (node.ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, (int32*)&mode,
sizeof(int32)) <= 0) {
mode = kByNameItem;
}
BString queryString;
switch (mode) {
case kByForumlaItem:
{
BString buffer;
if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK)
queryString << buffer;
break;
}
case kByNameItem:
{
BString buffer;
if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK)
queryString << "(name==*" << buffer << "*)";
break;
}
case kByAttributeItem:
{
int32 count = 1;
if (node.ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0,
(int32*)&count, sizeof(int32)) <= 0) {
count = 1;
}
attr_info info;
if (node.GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK)
break;
if (count > 1)
queryString << "(";
char* buffer = new char[info.size];
if (node.ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0,
buffer, (size_t)info.size) == info.size) {
BMessage message;
if (message.Unflatten(buffer) == B_OK) {
for (int32 index = 0; ; index++) {
const char* field;
const char* value;
if (message.FindString("menuSelection", index, &field)
!= B_OK
|| message.FindString("attrViewText", index, &value)
!= B_OK) {
break;
}
if (strcmp(field, "BEOS:TYPE") != 0) {
queryString << "(" << field << "==\""
<< value << "\")";
int32 logicMenuSelectedIndex;
if (message.FindInt32("logicalRelation", index,
&logicMenuSelectedIndex) == B_OK) {
if (logicMenuSelectedIndex == 0)
queryString << "&&";
else if (logicMenuSelectedIndex == 1)
queryString << "||";
} else
break;
}
}
}
}
if (count > 1)
queryString << ")";
delete [] buffer;
break;
}
default:
break;
}
if (queryString.Length() == 0)
return NULL;
if (queryString.FindFirst("text/x-email") < 0) {
BString temp;
temp << "(" << queryString << "&&(BEOS:TYPE==\"text/x-email\"))";
queryString = temp;
}
return strdup(queryString.String());
}
void
TMailWindow::_LaunchQuery(const char* title, const char* attribute,
BString text)
{
text.ReplaceAll(" ", "*");
text.ReplaceAll("\"", "\\\"");
BString* term = new BString("((");
term->Append(attribute);
term->Append("==\"*");
term->Append(text);
term->Append("*\")&&(BEOS:TYPE==\"text/x-email\"))");
BPath queryPath;
if (find_directory(B_USER_CACHE_DIRECTORY, &queryPath) != B_OK)
return;
queryPath.Append("Mail");
if ((create_directory(queryPath.Path(), 0777)) != B_OK)
return;
queryPath.Append(title);
BFile query(queryPath.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
if (query.InitCheck() != B_OK)
return;
BNode queryNode(queryPath.Path());
if (queryNode.InitCheck() != B_OK)
return;
BPath templatePath;
find_directory(B_USER_SETTINGS_DIRECTORY, &templatePath);
templatePath.Append("Tracker/DefaultQueryTemplates/text_x-email");
BNode templateNode(templatePath.Path());
if (templateNode.InitCheck() == B_OK) {
if (CopyAttributes(templateNode, queryNode) != B_OK) {
syslog(LOG_INFO, "Mail: copying x-email DefaultQueryTemplate "
"attributes failed");
}
}
queryNode.WriteAttrString("_trk/qrystr", term);
BNodeInfo nodeInfo(&queryNode);
nodeInfo.SetType("application/x-vnd.Be-query");
BEntry entry(queryPath.Path());
entry_ref ref;
if (entry.GetRef(&ref) == B_OK)
be_roster->Launch(&ref);
}
void
TMailWindow::_CreateNewPerson(BString address, BString name)
{
BMessage message(M_LAUNCH_PEOPLE);
message.AddString("META:name", name);
message.AddString("META:email", address);
status_t result = be_roster->Launch("application/x-person", &message);
if ((result != B_OK) && (result != B_ALREADY_RUNNING)) {
BAlert* alert = new BAlert("", B_TRANSLATE(
"Sorry, could not find an application that "
"supports the 'Person' data type."),
B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
}
void
TMailWindow::_AddReadButton()
{
BNode node(fRef);
read_flags flag = B_UNREAD;
read_read_attr(node, flag);
if (flag == B_READ) {
fToolBar->SetActionVisible(M_UNREAD, true);
fToolBar->SetActionVisible(M_READ, false);
} else {
fToolBar->SetActionVisible(M_UNREAD, false);
fToolBar->SetActionVisible(M_READ, true);
}
}
void
TMailWindow::_UpdateReadButton()
{
if (fApp->ShowToolBar()) {
if (!fAutoMarkRead && fIncoming)
_AddReadButton();
}
UpdateViews();
}
void
TMailWindow::_UpdateLabel(uint32 command, const char* label, bool show)
{
BButton* button = fToolBar->FindButton(command);
if (button != NULL) {
button->SetLabel(show ? label : NULL);
button->SetToolTip(show ? NULL : label);
}
}
void
TMailWindow::_SetDownloading(bool downloading)
{
fDownloading = downloading;
}
uint32
TMailWindow::_CurrentCharacterSet() const
{
uint32 defaultCharSet = fResending || !fIncoming
? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION;
BMenuItem* marked = fEncodingMenu->FindMarked();
if (marked == NULL)
return defaultCharSet;
return marked->Message()->GetInt32("charset", defaultCharSet);
}