1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2001, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
23CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or
30registered trademarks of Be Incorporated in the United States and other
31countries. Other brand product names are registered trademarks or trademarks
32of their respective holders. All rights reserved.
33*/
34
35
36#include "MailWindow.h"
37
38#include <fcntl.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <strings.h>
42#include <sys/stat.h>
43#include <sys/utsname.h>
44#include <syslog.h>
45#include <unistd.h>
46
47#include <AppFileInfo.h>
48#include <Autolock.h>
49#include <Bitmap.h>
50#include <Button.h>
51#include <CharacterSet.h>
52#include <CharacterSetRoster.h>
53#include <Clipboard.h>
54#include <Debug.h>
55#include <E-mail.h>
56#include <File.h>
57#include <IconUtils.h>
58#include <LayoutBuilder.h>
59#include <Locale.h>
60#include <Node.h>
61#include <PathMonitor.h>
62#include <PrintJob.h>
63#include <Query.h>
64#include <Resources.h>
65#include <Roster.h>
66#include <Screen.h>
67#include <String.h>
68#include <StringView.h>
69#include <TextView.h>
70#include <UTF8.h>
71#include <VolumeRoster.h>
72
73#include <fs_index.h>
74#include <fs_info.h>
75
76#include <MailMessage.h>
77#include <MailSettings.h>
78#include <MailDaemon.h>
79#include <mail_util.h>
80
81#include <CharacterSetRoster.h>
82
83#include "AttributeUtilities.h"
84#include "Content.h"
85#include "Enclosures.h"
86#include "FieldMsg.h"
87#include "FindWindow.h"
88#include "Header.h"
89#include "Messages.h"
90#include "MailApp.h"
91#include "MailPopUpMenu.h"
92#include "MailSupport.h"
93#include "Prefs.h"
94#include "QueryMenu.h"
95#include "Signature.h"
96#include "Settings.h"
97#include "Status.h"
98#include "String.h"
99#include "Utilities.h"
100
101
102#define B_TRANSLATION_CONTEXT "Mail"
103
104
105using namespace BPrivate;
106
107
108const char* kUndoStrings[] = {
109	"Undo",
110	"Undo typing",
111	"Undo cut",
112	"Undo paste",
113	"Undo clear",
114	"Undo drop"
115};
116
117const char* kRedoStrings[] = {
118	"Redo",
119	"Redo typing",
120	"Redo cut",
121	"Redo paste",
122	"Redo clear",
123	"Redo drop"
124};
125
126
127// Text for both the main menu and the pop-up menu.
128static const char* kSpamMenuItemTextArray[] = {
129	"Mark as spam and move to trash",		// M_TRAIN_SPAM_AND_DELETE
130	"Mark as spam",							// M_TRAIN_SPAM
131	"Unmark this message",					// M_UNTRAIN
132	"Mark as genuine"						// M_TRAIN_GENUINE
133};
134
135static const uint32 kMsgQuitAndKeepAllStatus = 'Casm';
136
137static const char* kQueriesDirectory = "mail/queries";
138static const char* kAttrQueryInitialMode = "_trk/qryinitmode";
139	// taken from src/kits/tracker/Attributes.h
140static const char* kAttrQueryInitialString = "_trk/qryinitstr";
141static const char* kAttrQueryInitialNumAttrs = "_trk/qryinitnumattrs";
142static const char* kAttrQueryInitialAttrs = "_trk/qryinitattrs";
143static const uint32 kAttributeItemMain = 'Fatr';
144	// taken from src/kits/tracker/FindPanel.h
145static const uint32 kByNameItem = 'Fbyn';
146	// taken from src/kits/tracker/FindPanel.h
147static const uint32 kByAttributeItem = 'Fbya';
148	// taken from src/kits/tracker/FindPanel.h
149static const uint32 kByForumlaItem = 'Fbyq';
150	// taken from src/kits/tracker/FindPanel.h
151static const int kCopyBufferSize = 64 * 1024;	// 64 KB
152
153static const char* kSameRecipientItem = B_TRANSLATE_MARK("Same recipient");
154static const char* kSameSenderItem = B_TRANSLATE_MARK("Same sender");
155static const char* kSameSubjectItem = B_TRANSLATE_MARK("Same subject");
156
157
158// static bitmap cache
159BObjectList<TMailWindow::BitmapItem> TMailWindow::sBitmapCache;
160BLocker TMailWindow::sBitmapCacheLock;
161
162// static list for tracking of Windows
163BList TMailWindow::sWindowList;
164BLocker TMailWindow::sWindowListLock;
165
166
167class HorizontalLine : public BView {
168public:
169	HorizontalLine(BRect rect)
170		:
171		BView (rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW)
172	{
173	}
174
175	virtual void Draw(BRect rect)
176	{
177		FillRect(rect, B_SOLID_HIGH);
178	}
179};
180
181
182//	#pragma mark -
183
184
185TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app,
186	const entry_ref* ref, const char* to, const BFont* font, bool resending,
187	BMessenger* messenger)
188	:
189	BWindow(rect, title, B_DOCUMENT_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS),
190
191	fApp(app),
192	fMail(NULL),
193	fRef(NULL),
194	fFieldState(0),
195	fPanel(NULL),
196	fSaveAddrMenu(NULL),
197	fLeaveStatusMenu(NULL),
198	fEncodingMenu(NULL),
199	fZoom(rect),
200	fEnclosuresView(NULL),
201	fPrevTrackerPositionSaved(false),
202	fNextTrackerPositionSaved(false),
203	fSigAdded(false),
204	fReplying(false),
205	fResending(resending),
206	fSent(false),
207	fDraft(false),
208	fChanged(false),
209	fOriginatingWindow(NULL),
210
211	fDownloading(false)
212{
213	fKeepStatusOnQuit = false;
214
215	if (messenger != NULL)
216		fTrackerMessenger = *messenger;
217
218	BFile file(ref, B_READ_ONLY);
219	if (ref) {
220		fRef = new entry_ref(*ref);
221		fIncoming = true;
222	} else
223		fIncoming = false;
224
225	fAutoMarkRead = fApp->AutoMarkRead();
226	fMenuBar = new BMenuBar("menuBar");
227
228	// File Menu
229
230	BMenu* menu = new BMenu(B_TRANSLATE("File"));
231
232	BMessage* msg = new BMessage(M_NEW);
233	msg->AddInt32("type", M_NEW);
234	BMenuItem* item = new BMenuItem(B_TRANSLATE("New mail message"), msg, 'N');
235	menu->AddItem(item);
236	item->SetTarget(be_app);
237
238	// Cheap hack - only show the drafts menu when composing messages.  Insert
239	// a "true || " in the following IF statement if you want the old BeMail
240	// behaviour.  The difference is that without live draft menu updating you
241	// can open around 100 e-mails (the BeOS maximum number of open files)
242	// rather than merely around 20, since each open draft-monitoring query
243	// sucks up one file handle per mounted BFS disk volume.  Plus mail file
244	// opening speed is noticably improved!  ToDo: change this to populate the
245	// Draft menu with the file names on demand - when the user clicks on it;
246	// don't need a live query since the menu isn't staying up for more than a
247	// few seconds.
248
249	if (!fIncoming) {
250		QueryMenu* queryMenu = new QueryMenu(B_TRANSLATE("Open draft"), false);
251		queryMenu->SetTargetForItems(be_app);
252
253		queryMenu->SetPredicate("MAIL:draft==1");
254		menu->AddItem(queryMenu);
255	}
256
257	if (!fIncoming || resending) {
258		menu->AddItem(fSendLater = new BMenuItem(B_TRANSLATE("Save as draft"),
259			new BMessage(M_SAVE_AS_DRAFT), 'S'));
260	}
261
262	if (!resending && fIncoming) {
263		menu->AddSeparatorItem();
264
265		BMenu* subMenu = new BMenu(B_TRANSLATE("Close and "));
266
267		read_flags flag;
268		read_read_attr(file, flag);
269
270		if (flag == B_UNREAD) {
271			subMenu->AddItem(item = new BMenuItem(
272				B_TRANSLATE_COMMENT("Leave as 'New'",
273				"Do not translate New - this is non-localizable e-mail status"),
274				new BMessage(kMsgQuitAndKeepAllStatus), 'W', B_SHIFT_KEY));
275		} else {
276			BString status;
277			file.ReadAttrString(B_MAIL_ATTR_STATUS, &status);
278
279			BString label;
280			if (status.Length() > 0)
281				label.SetToFormat(B_TRANSLATE("Leave as '%s'"),
282					status.String());
283			else
284				label = B_TRANSLATE("Leave same");
285
286			subMenu->AddItem(item = new BMenuItem(label.String(),
287							new BMessage(B_QUIT_REQUESTED), 'W'));
288			AddShortcut('W', B_COMMAND_KEY | B_SHIFT_KEY,
289				new BMessage(kMsgQuitAndKeepAllStatus));
290		}
291
292		subMenu->AddItem(new BMenuItem(B_TRANSLATE("Move to trash"),
293			new BMessage(M_DELETE), 'T', B_CONTROL_KEY));
294		AddShortcut('T', B_SHIFT_KEY | B_COMMAND_KEY,
295			new BMessage(M_DELETE_NEXT));
296
297		subMenu->AddSeparatorItem();
298
299		subMenu->AddItem(new BMenuItem(B_TRANSLATE("Set to Saved"),
300			new BMessage(M_CLOSE_SAVED), 'W', B_CONTROL_KEY));
301
302		if (add_query_menu_items(subMenu, INDEX_STATUS, M_STATUS,
303			B_TRANSLATE("Set to %s")) > 0)
304			subMenu->AddSeparatorItem();
305
306		subMenu->AddItem(new BMenuItem(B_TRANSLATE("Set to" B_UTF8_ELLIPSIS),
307			new BMessage(M_CLOSE_CUSTOM)));
308
309#if 0
310		subMenu->AddItem(new BMenuItem(new TMenu(
311			B_TRANSLATE("Set to" B_UTF8_ELLIPSIS), INDEX_STATUS, M_STATUS,
312				false, false),
313			new BMessage(M_CLOSE_CUSTOM)));
314#endif
315		menu->AddItem(subMenu);
316
317		fLeaveStatusMenu = subMenu;
318	} else {
319		menu->AddSeparatorItem();
320		menu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
321			new BMessage(B_CLOSE_REQUESTED), 'W'));
322	}
323
324	menu->AddSeparatorItem();
325	menu->AddItem(fPrint = new BMenuItem(
326		B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS),
327		new BMessage(M_PRINT_SETUP)));
328	menu->AddItem(fPrint = new BMenuItem(
329		B_TRANSLATE("Print" B_UTF8_ELLIPSIS),
330		new BMessage(M_PRINT), 'P'));
331	fMenuBar->AddItem(menu);
332
333	menu->AddSeparatorItem();
334	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Quit"),
335		new BMessage(B_QUIT_REQUESTED), 'Q'));
336	item->SetTarget(be_app);
337
338	// Edit Menu
339
340	menu = new BMenu(B_TRANSLATE("Edit"));
341	menu->AddItem(fUndo = new BMenuItem(B_TRANSLATE("Undo"),
342		new BMessage(B_UNDO), 'Z', 0));
343	fUndo->SetTarget(NULL, this);
344	menu->AddItem(fRedo = new BMenuItem(B_TRANSLATE("Redo"),
345		new BMessage(M_REDO), 'Z', B_SHIFT_KEY));
346	fRedo->SetTarget(NULL, this);
347	menu->AddSeparatorItem();
348	menu->AddItem(fCut = new BMenuItem(B_TRANSLATE("Cut"),
349		new BMessage(B_CUT), 'X'));
350	fCut->SetTarget(NULL, this);
351	menu->AddItem(fCopy = new BMenuItem(B_TRANSLATE("Copy"),
352		new BMessage(B_COPY), 'C'));
353	fCopy->SetTarget(NULL, this);
354	menu->AddItem(fPaste = new BMenuItem(B_TRANSLATE("Paste"),
355		new BMessage(B_PASTE),
356		'V'));
357	fPaste->SetTarget(NULL, this);
358	menu->AddSeparatorItem();
359	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Select all"),
360		new BMessage(M_SELECT), 'A'));
361	menu->AddSeparatorItem();
362	item->SetTarget(NULL, this);
363	menu->AddItem(new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS),
364		new BMessage(M_FIND), 'F'));
365	menu->AddItem(new BMenuItem(B_TRANSLATE("Find again"),
366		new BMessage(M_FIND_AGAIN), 'G'));
367	if (!fIncoming) {
368		menu->AddSeparatorItem();
369		fQuote = new BMenuItem(B_TRANSLATE("Quote"),
370			new BMessage(M_QUOTE), '\'');
371		menu->AddItem(fQuote);
372		fRemoveQuote = new BMenuItem(B_TRANSLATE("Remove quote"),
373			new BMessage(M_REMOVE_QUOTE), '\'', B_SHIFT_KEY);
374		menu->AddItem(fRemoveQuote);
375
376		menu->AddSeparatorItem();
377		fSpelling = new BMenuItem(B_TRANSLATE("Check spelling"),
378			new BMessage(M_CHECK_SPELLING), ';');
379		menu->AddItem(fSpelling);
380		if (fApp->StartWithSpellCheckOn())
381			PostMessage(M_CHECK_SPELLING);
382	}
383	menu->AddSeparatorItem();
384	menu->AddItem(item = new BMenuItem(
385		B_TRANSLATE("Settings" B_UTF8_ELLIPSIS),
386		new BMessage(M_PREFS),','));
387	item->SetTarget(be_app);
388	fMenuBar->AddItem(menu);
389	menu->AddItem(item = new BMenuItem(
390		B_TRANSLATE("Accounts" B_UTF8_ELLIPSIS),
391		new BMessage(M_ACCOUNTS),'-'));
392	item->SetTarget(be_app);
393
394	// View Menu
395
396	if (!resending && fIncoming) {
397		menu = new BMenu(B_TRANSLATE("View"));
398		menu->AddItem(fHeader = new BMenuItem(B_TRANSLATE("Show header"),
399			new BMessage(M_HEADER), 'H'));
400		menu->AddItem(fRaw = new BMenuItem(B_TRANSLATE("Show raw message"),
401			new BMessage(M_RAW)));
402		fMenuBar->AddItem(menu);
403	}
404
405	// Message Menu
406
407	menu = new BMenu(B_TRANSLATE("Message"));
408
409	if (!resending && fIncoming) {
410		BMenuItem* menuItem;
411		menu->AddItem(new BMenuItem(B_TRANSLATE("Reply"),
412			new BMessage(M_REPLY),'R'));
413		menu->AddItem(new BMenuItem(B_TRANSLATE("Reply to sender"),
414			new BMessage(M_REPLY_TO_SENDER),'R',B_OPTION_KEY));
415		menu->AddItem(new BMenuItem(B_TRANSLATE("Reply to all"),
416			new BMessage(M_REPLY_ALL), 'R', B_SHIFT_KEY));
417
418		menu->AddSeparatorItem();
419
420		menu->AddItem(new BMenuItem(B_TRANSLATE("Forward"),
421			new BMessage(M_FORWARD), 'J'));
422		menu->AddItem(new BMenuItem(B_TRANSLATE("Forward without attachments"),
423			new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS)));
424		menu->AddItem(menuItem = new BMenuItem(B_TRANSLATE("Resend"),
425			new BMessage(M_RESEND)));
426		menu->AddItem(menuItem = new BMenuItem(B_TRANSLATE("Copy to new"),
427			new BMessage(M_COPY_TO_NEW), 'D'));
428
429		menu->AddSeparatorItem();
430		fDeleteNext = new BMenuItem(B_TRANSLATE("Move to trash"),
431			new BMessage(M_DELETE_NEXT), 'T');
432		menu->AddItem(fDeleteNext);
433		menu->AddSeparatorItem();
434
435		fPrevMsg = new BMenuItem(B_TRANSLATE("Previous message"),
436			new BMessage(M_PREVMSG), B_UP_ARROW);
437		menu->AddItem(fPrevMsg);
438		fNextMsg = new BMenuItem(B_TRANSLATE("Next message"),
439			new BMessage(M_NEXTMSG), B_DOWN_ARROW);
440		menu->AddItem(fNextMsg);
441	} else {
442		menu->AddItem(fSendNow = new BMenuItem(B_TRANSLATE("Send message"),
443			new BMessage(M_SEND_NOW), 'M'));
444
445		if (!fIncoming) {
446			menu->AddSeparatorItem();
447			fSignature = new TMenu(B_TRANSLATE("Add signature"),
448				INDEX_SIGNATURE, M_SIGNATURE);
449			menu->AddItem(new BMenuItem(fSignature));
450			menu->AddItem(item = new BMenuItem(
451				B_TRANSLATE("Edit signatures" B_UTF8_ELLIPSIS),
452				new BMessage(M_EDIT_SIGNATURE)));
453			item->SetTarget(be_app);
454			menu->AddSeparatorItem();
455			menu->AddItem(fAdd = new BMenuItem(
456				B_TRANSLATE("Add enclosure" B_UTF8_ELLIPSIS),
457				new BMessage(M_ADD), 'E'));
458			menu->AddItem(fRemove = new BMenuItem(
459				B_TRANSLATE("Remove enclosure"),
460				new BMessage(M_REMOVE), 'T'));
461		}
462	}
463	if (fIncoming) {
464		menu->AddSeparatorItem();
465		fSaveAddrMenu = new BMenu(B_TRANSLATE("Save address"));
466		menu->AddItem(fSaveAddrMenu);
467	}
468
469	// Encoding menu
470
471	fEncodingMenu = new BMenu(B_TRANSLATE("Encoding"));
472
473	BMenuItem* automaticItem = NULL;
474	if (!resending && fIncoming) {
475		// Reading a message, display the Automatic item
476		msg = new BMessage(CHARSET_CHOICE_MADE);
477		msg->AddInt32("charset", B_MAIL_NULL_CONVERSION);
478		automaticItem = new BMenuItem(B_TRANSLATE("Automatic"), msg);
479		fEncodingMenu->AddItem(automaticItem);
480		fEncodingMenu->AddSeparatorItem();
481	}
482
483	uint32 defaultCharSet = resending || !fIncoming
484		? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION;
485	bool markedCharSet = false;
486
487	BCharacterSetRoster roster;
488	BCharacterSet charSet;
489	while (roster.GetNextCharacterSet(&charSet) == B_OK) {
490		BString name(charSet.GetPrintName());
491		const char* mime = charSet.GetMIMEName();
492		if (mime != NULL)
493			name << " (" << mime << ")";
494
495		uint32 convertID;
496		if (mime == NULL || strcasecmp(mime, "UTF-8") != 0)
497			convertID = charSet.GetConversionID();
498		else
499			convertID = B_MAIL_UTF8_CONVERSION;
500
501		msg = new BMessage(CHARSET_CHOICE_MADE);
502		msg->AddInt32("charset", convertID);
503		fEncodingMenu->AddItem(item = new BMenuItem(name.String(), msg));
504		if (convertID == defaultCharSet && !markedCharSet) {
505			item->SetMarked(true);
506			markedCharSet = true;
507		}
508	}
509
510	msg = new BMessage(CHARSET_CHOICE_MADE);
511	msg->AddInt32("charset", B_MAIL_US_ASCII_CONVERSION);
512	fEncodingMenu->AddItem(item = new BMenuItem("US-ASCII", msg));
513	if (defaultCharSet == B_MAIL_US_ASCII_CONVERSION && !markedCharSet) {
514		item->SetMarked(true);
515		markedCharSet = true;
516	}
517
518	if (automaticItem != NULL && !markedCharSet)
519		automaticItem->SetMarked(true);
520
521	menu->AddSeparatorItem();
522	menu->AddItem(fEncodingMenu);
523	fMenuBar->AddItem(menu);
524	fEncodingMenu->SetRadioMode(true);
525	fEncodingMenu->SetTargetForItems(this);
526
527	// Spam Menu
528
529	if (!resending && fIncoming && fApp->ShowSpamGUI()) {
530		menu = new BMenu("Spam filtering");
531		menu->AddItem(new BMenuItem("Mark as spam and move to trash",
532			new BMessage(M_TRAIN_SPAM_AND_DELETE), 'K'));
533		menu->AddItem(new BMenuItem("Mark as spam",
534			new BMessage(M_TRAIN_SPAM), 'K', B_OPTION_KEY));
535		menu->AddSeparatorItem();
536		menu->AddItem(new BMenuItem("Unmark this message",
537			new BMessage(M_UNTRAIN)));
538		menu->AddSeparatorItem();
539		menu->AddItem(new BMenuItem("Mark as genuine",
540			new BMessage(M_TRAIN_GENUINE), 'K', B_SHIFT_KEY));
541		fMenuBar->AddItem(menu);
542	}
543
544	// Queries Menu
545
546	fQueryMenu = new BMenu(B_TRANSLATE("Queries"));
547	fMenuBar->AddItem(fQueryMenu);
548
549	_RebuildQueryMenu(true);
550
551	// Button Bar
552
553	BuildToolBar();
554
555	if (!fApp->ShowToolBar())
556		fToolBar->Hide();
557
558	fHeaderView = new THeaderView(fIncoming, resending,
559		fApp->DefaultAccount());
560
561	fContentView = new TContentView(fIncoming, const_cast<BFont*>(font),
562		false, fApp->ColoredQuotes());
563		// TContentView needs to be properly const, for now cast away constness
564
565	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
566		.Add(fMenuBar)
567		.Add(fToolBar)
568		.AddGroup(B_VERTICAL, 0)
569			.Add(fHeaderView)
570			.SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING)
571		.End()
572		.Add(fContentView);
573
574	if (to != NULL)
575		fHeaderView->SetTo(to);
576
577	AddShortcut('n', B_COMMAND_KEY, new BMessage(M_NEW));
578
579	// If auto-signature, add signature to the text here.
580
581	BString signature = fApp->Signature();
582
583	if (!fIncoming && strcmp(signature.String(), B_TRANSLATE("None")) != 0) {
584		if (strcmp(signature.String(), B_TRANSLATE("Random")) == 0)
585			PostMessage(M_RANDOM_SIG);
586		else {
587			// Create a query to find this signature
588			BVolume volume;
589			BVolumeRoster().GetBootVolume(&volume);
590
591			BQuery query;
592			query.SetVolume(&volume);
593			query.PushAttr(INDEX_SIGNATURE);
594			query.PushString(signature.String());
595			query.PushOp(B_EQ);
596			query.Fetch();
597
598			// If we find the named query, add it to the text.
599			BEntry entry;
600			if (query.GetNextEntry(&entry) == B_NO_ERROR) {
601				BFile file;
602				file.SetTo(&entry, O_RDWR);
603				if (file.InitCheck() == B_NO_ERROR) {
604					entry_ref ref;
605					entry.GetRef(&ref);
606
607					BMessage msg(M_SIGNATURE);
608					msg.AddRef("ref", &ref);
609					PostMessage(&msg);
610				}
611			} else {
612				char tempString [2048];
613				query.GetPredicate (tempString, sizeof (tempString));
614				printf ("Query failed, was looking for: %s\n", tempString);
615			}
616		}
617	}
618
619	OpenMessage(ref, _CurrentCharacterSet());
620
621	AddShortcut('q', B_SHIFT_KEY, new BMessage(kMsgQuitAndKeepAllStatus));
622}
623
624
625BBitmap*
626TMailWindow::_RetrieveVectorIcon(int32 id)
627{
628	// Lock access to the list
629	BAutolock lock(sBitmapCacheLock);
630	if (!lock.IsLocked())
631		return NULL;
632
633	// Check for the bitmap in the cache first
634	BitmapItem* item;
635	for (int32 i = 0; (item = sBitmapCache.ItemAt(i)) != NULL; i++) {
636		if (item->id == id)
637			return item->bm;
638	}
639
640	// If it's not in the cache, try to load it
641	BResources* res = BApplication::AppResources();
642	if (res == NULL)
643		return NULL;
644	size_t size;
645	const void* data = res->LoadResource(B_VECTOR_ICON_TYPE, id, &size);
646
647	if (!data)
648		return NULL;
649
650	BBitmap* bitmap = new BBitmap(BRect(0, 0, 21, 21), B_RGBA32);
651	status_t status = BIconUtils::GetVectorIcon((uint8*)data, size, bitmap);
652	if (status == B_OK) {
653		item = (BitmapItem*)malloc(sizeof(BitmapItem));
654		item->bm = bitmap;
655		item->id = id;
656		sBitmapCache.AddItem(item);
657		return bitmap;
658	}
659
660	return NULL;
661}
662
663
664void
665TMailWindow::BuildToolBar()
666{
667	fToolBar = new BToolBar();
668	fToolBar->AddAction(M_NEW, this, _RetrieveVectorIcon(11), NULL,
669		B_TRANSLATE("New"));
670	fToolBar->AddSeparator();
671
672	if (fResending) {
673		fToolBar->AddAction(M_SEND_NOW, this, _RetrieveVectorIcon(1), NULL,
674			B_TRANSLATE("Send"));
675	} else if (!fIncoming) {
676		fToolBar->AddAction(M_SEND_NOW, this, _RetrieveVectorIcon(1), NULL,
677			B_TRANSLATE("Send"));
678		fToolBar->SetActionEnabled(M_SEND_NOW, false);
679		fToolBar->AddAction(M_SIG_MENU, this, _RetrieveVectorIcon(2), NULL,
680			B_TRANSLATE("Signature"));
681		fToolBar->AddAction(M_SAVE_AS_DRAFT, this, _RetrieveVectorIcon(3), NULL,
682			B_TRANSLATE("Save"));
683		fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false);
684		fToolBar->AddAction(M_PRINT, this, _RetrieveVectorIcon(5), NULL,
685			B_TRANSLATE("Print"));
686		fToolBar->SetActionEnabled(M_PRINT, false);
687		fToolBar->AddAction(M_DELETE, this, _RetrieveVectorIcon(4), NULL,
688			B_TRANSLATE("Trash"));
689	} else {
690		fToolBar->AddAction(M_REPLY, this, _RetrieveVectorIcon(8), NULL,
691			B_TRANSLATE("Reply"));
692		fToolBar->AddAction(M_FORWARD, this, _RetrieveVectorIcon(9), NULL,
693			B_TRANSLATE("Forward"));
694		fToolBar->AddAction(M_PRINT, this, _RetrieveVectorIcon(5), NULL,
695			B_TRANSLATE("Print"));
696		fToolBar->AddAction(M_DELETE_NEXT, this, _RetrieveVectorIcon(4), NULL,
697			B_TRANSLATE("Trash"));
698		if (fApp->ShowSpamGUI()) {
699			fToolBar->AddAction(M_SPAM_BUTTON, this, _RetrieveVectorIcon(10),
700				NULL, B_TRANSLATE("Spam"));
701		}
702		fToolBar->AddSeparator();
703		fToolBar->AddAction(M_NEXTMSG, this, _RetrieveVectorIcon(6), NULL,
704			B_TRANSLATE("Next"));
705		fToolBar->AddAction(M_UNREAD, this, _RetrieveVectorIcon(12), NULL,
706			B_TRANSLATE("Unread"));
707		fToolBar->SetActionVisible(M_UNREAD, false);
708		fToolBar->AddAction(M_READ, this, _RetrieveVectorIcon(13), NULL,
709			B_TRANSLATE(" Read "));
710		fToolBar->SetActionVisible(M_READ, false);
711		fToolBar->AddAction(M_PREVMSG, this, _RetrieveVectorIcon(7), NULL,
712			B_TRANSLATE("Previous"));
713
714		if (!fTrackerMessenger.IsValid()) {
715			fToolBar->SetActionEnabled(M_NEXTMSG, false);
716			fToolBar->SetActionEnabled(M_PREVMSG, false);
717		}
718
719		if (!fAutoMarkRead)
720			_AddReadButton();
721	}
722	fToolBar->AddGlue();
723}
724
725
726void
727TMailWindow::UpdateViews()
728{
729	uint8 showToolBar = fApp->ShowToolBar();
730
731	// Show/Hide Button Bar
732	if (showToolBar) {
733		if (fToolBar->IsHidden())
734			fToolBar->Show();
735
736		bool showLabel = showToolBar == kShowToolBar;
737		_UpdateLabel(M_NEW, B_TRANSLATE("New"), showLabel);
738		_UpdateLabel(M_SEND_NOW, B_TRANSLATE("Send"), showLabel);
739		_UpdateLabel(M_SIG_MENU, B_TRANSLATE("Signature"), showLabel);
740		_UpdateLabel(M_SAVE_AS_DRAFT, B_TRANSLATE("Save"), showLabel);
741		_UpdateLabel(M_PRINT, B_TRANSLATE("Print"), showLabel);
742		_UpdateLabel(M_DELETE, B_TRANSLATE("Trash"), showLabel);
743		_UpdateLabel(M_REPLY, B_TRANSLATE("Reply"), showLabel);
744		_UpdateLabel(M_FORWARD, B_TRANSLATE("Forward"), showLabel);
745		_UpdateLabel(M_DELETE_NEXT, B_TRANSLATE("Trash"), showLabel);
746		_UpdateLabel(M_SPAM_BUTTON, B_TRANSLATE("Spam"), showLabel);
747		_UpdateLabel(M_NEXTMSG, B_TRANSLATE("Next"), showLabel);
748		_UpdateLabel(M_UNREAD, B_TRANSLATE("Unread"), showLabel);
749		_UpdateLabel(M_READ, B_TRANSLATE(" Read "), showLabel);
750		_UpdateLabel(M_PREVMSG, B_TRANSLATE("Previous"), showLabel);
751	} else if (!fToolBar->IsHidden())
752		fToolBar->Hide();
753}
754
755
756void
757TMailWindow::UpdatePreferences()
758{
759	fAutoMarkRead = fApp->AutoMarkRead();
760
761	_UpdateReadButton();
762}
763
764
765TMailWindow::~TMailWindow()
766{
767	fApp->SetLastWindowFrame(Frame());
768
769	delete fMail;
770	delete fPanel;
771	delete fOriginatingWindow;
772	delete fRef;
773
774	BAutolock locker(sWindowListLock);
775	sWindowList.RemoveItem(this);
776}
777
778
779status_t
780TMailWindow::GetMailNodeRef(node_ref& nodeRef) const
781{
782	if (fRef == NULL)
783		return B_ERROR;
784
785	BNode node(fRef);
786	return node.GetNodeRef(&nodeRef);
787}
788
789
790bool
791TMailWindow::GetTrackerWindowFile(entry_ref* ref, bool next) const
792{
793	// Position was already saved
794	if (next && fNextTrackerPositionSaved) {
795		*ref = fNextRef;
796		return true;
797	}
798	if (!next && fPrevTrackerPositionSaved) {
799		*ref = fPrevRef;
800		return true;
801	}
802
803	if (!fTrackerMessenger.IsValid())
804		return false;
805
806	// Ask the tracker what the next/prev file in the window is.
807	// Continue asking for the next reference until a valid
808	// email file is found (ignoring other types).
809	entry_ref nextRef = *ref;
810	bool foundRef = false;
811	while (!foundRef) {
812		BMessage request(B_GET_PROPERTY);
813		BMessage spc;
814		if (next)
815			spc.what = 'snxt';
816		else
817			spc.what = 'sprv';
818
819		spc.AddString("property", "Entry");
820		spc.AddRef("data", &nextRef);
821
822		request.AddSpecifier(&spc);
823		BMessage reply;
824		if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK)
825			return false;
826
827		if (reply.FindRef("result", &nextRef) != B_OK)
828			return false;
829
830		char fileType[256];
831		BNode node(&nextRef);
832		if (node.InitCheck() != B_OK)
833			return false;
834
835		if (BNodeInfo(&node).GetType(fileType) != B_OK)
836			return false;
837
838		if (strcasecmp(fileType, B_MAIL_TYPE) == 0
839			|| strcasecmp(fileType, B_PARTIAL_MAIL_TYPE) == 0)
840			foundRef = true;
841	}
842
843	*ref = nextRef;
844	return foundRef;
845}
846
847
848void
849TMailWindow::SaveTrackerPosition(entry_ref* ref)
850{
851	// if only one of them is saved, we're not going to do it again
852	if (fNextTrackerPositionSaved || fPrevTrackerPositionSaved)
853		return;
854
855	fNextRef = fPrevRef = *ref;
856
857	fNextTrackerPositionSaved = GetTrackerWindowFile(&fNextRef, true);
858	fPrevTrackerPositionSaved = GetTrackerWindowFile(&fPrevRef, false);
859}
860
861
862void
863TMailWindow::SetOriginatingWindow(BWindow* window)
864{
865	delete fOriginatingWindow;
866	fOriginatingWindow = new BMessenger(window);
867}
868
869
870void
871TMailWindow::SetTrackerSelectionToCurrent()
872{
873	BMessage setSelection(B_SET_PROPERTY);
874	setSelection.AddSpecifier("Selection");
875	setSelection.AddRef("data", fRef);
876
877	fTrackerMessenger.SendMessage(&setSelection);
878}
879
880
881void
882TMailWindow::PreserveReadingPos(bool save)
883{
884	BScrollBar* scroll = fContentView->TextView()->ScrollBar(B_VERTICAL);
885	if (scroll == NULL || fRef == NULL)
886		return;
887
888	BNode node(fRef);
889	float pos = scroll->Value();
890
891	const char* name = "MAIL:read_pos";
892	if (save) {
893		node.WriteAttr(name, B_FLOAT_TYPE, 0, &pos, sizeof(pos));
894		return;
895	}
896
897	if (node.ReadAttr(name, B_FLOAT_TYPE, 0, &pos, sizeof(pos)) == sizeof(pos)) {
898		Lock();
899		scroll->SetValue(pos);
900		Unlock();
901	}
902}
903
904
905void
906TMailWindow::MarkMessageRead(entry_ref* message, read_flags flag)
907{
908	BNode node(message);
909	status_t status = node.InitCheck();
910	if (status != B_OK)
911		return;
912
913	int32 account;
914	if (node.ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &account,
915		sizeof(account)) < 0)
916		account = -1;
917
918	// don't wait for the server write the attribute directly
919	write_read_attr(node, flag);
920
921	// preserve the read position in the node attribute
922	PreserveReadingPos(true);
923
924	BMailDaemon().MarkAsRead(account, *message, flag);
925}
926
927
928void
929TMailWindow::FrameResized(float width, float height)
930{
931	fContentView->FrameResized(width, height);
932}
933
934
935void
936TMailWindow::MenusBeginning()
937{
938	int32 finish = 0;
939	int32 start = 0;
940
941	if (!fIncoming) {
942		bool gotToField = !fHeaderView->IsToEmpty();
943		bool gotCcField = !fHeaderView->IsCcEmpty();
944		bool gotBccField = !fHeaderView->IsBccEmpty();
945		bool gotSubjectField = !fHeaderView->IsSubjectEmpty();
946		bool gotText = fContentView->TextView()->Text()[0] != 0;
947		fSendNow->SetEnabled(gotToField || gotBccField);
948		fSendLater->SetEnabled(fChanged && (gotToField || gotCcField
949			|| gotBccField || gotSubjectField || gotText));
950
951		be_clipboard->Lock();
952		fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain",
953				B_MIME_TYPE)
954			&& (fEnclosuresView == NULL || !fEnclosuresView->fList->IsFocus()));
955		be_clipboard->Unlock();
956
957		fQuote->SetEnabled(false);
958		fRemoveQuote->SetEnabled(false);
959
960		fAdd->SetEnabled(true);
961		fRemove->SetEnabled(fEnclosuresView != NULL
962			&& fEnclosuresView->fList->CurrentSelection() >= 0);
963	} else {
964		if (fResending) {
965			bool enable = !fHeaderView->IsToEmpty();
966			fSendNow->SetEnabled(enable);
967			//fSendLater->SetEnabled(enable);
968
969			if (fHeaderView->ToControl()->HasFocus()) {
970				fHeaderView->ToControl()->GetSelection(&start, &finish);
971
972				fCut->SetEnabled(start != finish);
973				be_clipboard->Lock();
974				fPaste->SetEnabled(be_clipboard->Data()->HasData(
975					"text/plain", B_MIME_TYPE));
976				be_clipboard->Unlock();
977			} else {
978				fCut->SetEnabled(false);
979				fPaste->SetEnabled(false);
980			}
981		} else {
982			fCut->SetEnabled(false);
983			fPaste->SetEnabled(false);
984		}
985	}
986
987	fPrint->SetEnabled(fContentView->TextView()->TextLength());
988
989	BTextView* textView = dynamic_cast<BTextView*>(CurrentFocus());
990	if (textView != NULL
991		&& (dynamic_cast<AddressTextControl*>(textView->Parent()) != NULL
992			|| dynamic_cast<BTextControl*>(textView->Parent()) != NULL)) {
993		// one of To:, Subject:, Account:, Cc:, Bcc:
994		textView->GetSelection(&start, &finish);
995	} else if (fContentView->TextView()->IsFocus()) {
996		fContentView->TextView()->GetSelection(&start, &finish);
997		if (!fIncoming) {
998			fQuote->SetEnabled(true);
999			fRemoveQuote->SetEnabled(true);
1000		}
1001	}
1002
1003	fCopy->SetEnabled(start != finish);
1004	if (!fIncoming)
1005		fCut->SetEnabled(start != finish);
1006
1007	// Undo stuff
1008	bool isRedo = false;
1009	undo_state undoState = B_UNDO_UNAVAILABLE;
1010
1011	BTextView* focusTextView = dynamic_cast<BTextView*>(CurrentFocus());
1012	if (focusTextView != NULL)
1013		undoState = focusTextView->UndoState(&isRedo);
1014
1015//	fUndo->SetLabel((isRedo)
1016//	? kRedoStrings[undoState] : kUndoStrings[undoState]);
1017	fUndo->SetEnabled(undoState != B_UNDO_UNAVAILABLE);
1018
1019	if (fLeaveStatusMenu != NULL && fRef != NULL) {
1020		BFile file(fRef, B_READ_ONLY);
1021		BString status;
1022		file.ReadAttrString(B_MAIL_ATTR_STATUS, &status);
1023
1024		BMenuItem* LeaveStatus = fLeaveStatusMenu->FindItem(B_QUIT_REQUESTED);
1025		if (LeaveStatus == NULL)
1026			LeaveStatus = fLeaveStatusMenu->FindItem(kMsgQuitAndKeepAllStatus);
1027
1028		if (LeaveStatus != NULL && status.Length() > 0) {
1029			BString label;
1030			label.SetToFormat(B_TRANSLATE("Leave as '%s'"), status.String());
1031			LeaveStatus->SetLabel(label.String());
1032		}
1033	}
1034}
1035
1036
1037void
1038TMailWindow::MessageReceived(BMessage* msg)
1039{
1040	bool wasReadMsg = false;
1041	switch (msg->what) {
1042		case B_MAIL_BODY_FETCHED:
1043		{
1044			status_t status = msg->FindInt32("status");
1045			if (status != B_OK) {
1046				fprintf(stderr, "Body could not be fetched: %s\n", strerror(status));
1047				PostMessage(B_QUIT_REQUESTED);
1048				break;
1049			}
1050
1051			entry_ref ref;
1052			if (msg->FindRef("ref", &ref) != B_OK)
1053				break;
1054			if (ref != *fRef)
1055				break;
1056
1057			// reload the current message
1058			OpenMessage(&ref, _CurrentCharacterSet());
1059			break;
1060		}
1061
1062		case FIELD_CHANGED:
1063		{
1064			int32 prevState = fFieldState;
1065			int32 fieldMask = msg->FindInt32("bitmask");
1066			void* source;
1067
1068			if (msg->FindPointer("source", &source) == B_OK) {
1069				int32 length;
1070
1071				if (fieldMask == FIELD_BODY)
1072					length = ((TTextView*)source)->TextLength();
1073				else
1074					length = ((AddressTextControl*)source)->TextLength();
1075
1076				if (length)
1077					fFieldState |= fieldMask;
1078				else
1079					fFieldState &= ~fieldMask;
1080			}
1081
1082			// Has anything changed?
1083			if (prevState != fFieldState || !fChanged) {
1084				// Change Buttons to reflect this
1085				fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, fFieldState);
1086				fToolBar->SetActionEnabled(M_PRINT, fFieldState);
1087				fToolBar->SetActionEnabled(M_SEND_NOW, (fFieldState & FIELD_TO)
1088					|| (fFieldState & FIELD_BCC));
1089			}
1090			fChanged = true;
1091
1092			// Update title bar if "subject" has changed
1093			if (!fIncoming && (fieldMask & FIELD_SUBJECT) != 0) {
1094				// If no subject, set to "Mail"
1095				if (fHeaderView->IsSubjectEmpty())
1096					SetTitle(B_TRANSLATE_SYSTEM_NAME("Mail"));
1097				else
1098					SetTitle(fHeaderView->Subject());
1099			}
1100			break;
1101		}
1102		case LIST_INVOKED:
1103			PostMessage(msg, fEnclosuresView);
1104			break;
1105
1106		case CHANGE_FONT:
1107			PostMessage(msg, fContentView);
1108			break;
1109
1110		case M_NEW:
1111		{
1112			BMessage message(M_NEW);
1113			message.AddInt32("type", msg->what);
1114			be_app->PostMessage(&message);
1115			break;
1116		}
1117
1118		case M_SPAM_BUTTON:
1119		{
1120			/*
1121				A popup from a button is good only when the behavior has some
1122				consistency and there is some visual indication that a menu
1123				will be shown when clicked. A workable implementation would
1124				have an extra button attached to the main one which has a
1125				downward-pointing arrow. Mozilla Thunderbird's 'Get Mail'
1126				button is a good example of this.
1127
1128				TODO: Replace this code with a split toolbar button
1129			*/
1130			uint32 buttons;
1131			if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK
1132				&& buttons == B_SECONDARY_MOUSE_BUTTON) {
1133				BPopUpMenu menu("Spam Actions", false, false);
1134				for (int i = 0; i < 4; i++)
1135					menu.AddItem(new BMenuItem(kSpamMenuItemTextArray[i],
1136						new BMessage(M_TRAIN_SPAM_AND_DELETE + i)));
1137
1138				BPoint where;
1139				msg->FindPoint("where", &where);
1140				BMenuItem* item;
1141				if ((item = menu.Go(where, false, false)) != NULL)
1142					PostMessage(item->Message());
1143				break;
1144			} else {
1145				// Default action for left clicking on the spam button.
1146				PostMessage(new BMessage(M_TRAIN_SPAM_AND_DELETE));
1147			}
1148			break;
1149		}
1150
1151		case M_TRAIN_SPAM_AND_DELETE:
1152			PostMessage(M_DELETE_NEXT);
1153		case M_TRAIN_SPAM:
1154			TrainMessageAs("Spam");
1155			break;
1156
1157		case M_UNTRAIN:
1158			TrainMessageAs("Uncertain");
1159			break;
1160
1161		case M_TRAIN_GENUINE:
1162			TrainMessageAs("Genuine");
1163			break;
1164
1165		case M_REPLY:
1166		{
1167			// TODO: This needs removed in favor of a split toolbar button.
1168			// See comments for Spam button
1169			uint32 buttons;
1170			if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK
1171				&& buttons == B_SECONDARY_MOUSE_BUTTON) {
1172				BPopUpMenu menu("Reply To", false, false);
1173				menu.AddItem(new BMenuItem(B_TRANSLATE("Reply"),
1174					new BMessage(M_REPLY)));
1175				menu.AddItem(new BMenuItem(B_TRANSLATE("Reply to sender"),
1176					new BMessage(M_REPLY_TO_SENDER)));
1177				menu.AddItem(new BMenuItem(B_TRANSLATE("Reply to all"),
1178					new BMessage(M_REPLY_ALL)));
1179
1180				BPoint where;
1181				msg->FindPoint("where", &where);
1182
1183				BMenuItem* item;
1184				if ((item = menu.Go(where, false, false)) != NULL) {
1185					item->SetTarget(this);
1186					PostMessage(item->Message());
1187				}
1188				break;
1189			}
1190			// Fall through
1191		}
1192		case M_FORWARD:
1193		{
1194			// TODO: This needs removed in favor of a split toolbar button.
1195			// See comments for Spam button
1196			uint32 buttons;
1197			if (msg->FindInt32("buttons", (int32*)&buttons) == B_OK
1198				&& buttons == B_SECONDARY_MOUSE_BUTTON) {
1199				BPopUpMenu menu("Forward", false, false);
1200				menu.AddItem(new BMenuItem(B_TRANSLATE("Forward"),
1201					new BMessage(M_FORWARD)));
1202				menu.AddItem(new BMenuItem(
1203					B_TRANSLATE("Forward without attachments"),
1204					new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS)));
1205
1206				BPoint where;
1207				msg->FindPoint("where", &where);
1208
1209				BMenuItem* item;
1210				if ((item = menu.Go(where, false, false)) != NULL) {
1211					item->SetTarget(this);
1212					PostMessage(item->Message());
1213				}
1214				break;
1215			}
1216		}
1217
1218		// Fall Through
1219		case M_REPLY_ALL:
1220		case M_REPLY_TO_SENDER:
1221		case M_FORWARD_WITHOUT_ATTACHMENTS:
1222		case M_RESEND:
1223		case M_COPY_TO_NEW:
1224		{
1225			BMessage message(M_NEW);
1226			message.AddRef("ref", fRef);
1227			message.AddPointer("window", this);
1228			message.AddInt32("type", msg->what);
1229			be_app->PostMessage(&message);
1230			break;
1231		}
1232		case M_DELETE:
1233		case M_DELETE_PREV:
1234		case M_DELETE_NEXT:
1235		{
1236			if (msg->what == M_DELETE_NEXT && (modifiers() & B_SHIFT_KEY) != 0)
1237				msg->what = M_DELETE_PREV;
1238
1239			bool foundRef = false;
1240			entry_ref nextRef;
1241			if ((msg->what == M_DELETE_PREV || msg->what == M_DELETE_NEXT)
1242				&& fRef != NULL) {
1243				// Find the next message that should be displayed
1244				nextRef = *fRef;
1245				foundRef = GetTrackerWindowFile(&nextRef,
1246					msg->what == M_DELETE_NEXT);
1247			}
1248			if (fIncoming) {
1249				read_flags flag = (fAutoMarkRead == true) ? B_READ : B_SEEN;
1250				MarkMessageRead(fRef, flag);
1251			}
1252
1253			if (!fTrackerMessenger.IsValid() || !fIncoming) {
1254				// Not associated with a tracker window.  Create a new
1255				// messenger and ask the tracker to delete this entry
1256				if (fDraft || fIncoming) {
1257					BMessenger tracker("application/x-vnd.Be-TRAK");
1258					if (tracker.IsValid()) {
1259						BMessage msg('Ttrs');
1260						msg.AddRef("refs", fRef);
1261						tracker.SendMessage(&msg);
1262					} else {
1263						BAlert* alert = new BAlert("",
1264							B_TRANSLATE("Need Tracker to move items to trash"),
1265							B_TRANSLATE("Sorry"));
1266						alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1267						alert->Go();
1268					}
1269				}
1270			} else {
1271				// This is associated with a tracker window.  Ask the
1272				// window to delete this entry.  Do it this way if we
1273				// can instead of the above way because it doesn't reset
1274				// the selection (even though we set selection below, this
1275				// still causes problems).
1276				BMessage delmsg(B_DELETE_PROPERTY);
1277				BMessage entryspec('sref');
1278				entryspec.AddRef("refs", fRef);
1279				entryspec.AddString("property", "Entry");
1280				delmsg.AddSpecifier(&entryspec);
1281				fTrackerMessenger.SendMessage(&delmsg);
1282			}
1283
1284			// 	If the next file was found, open it.  If it was not,
1285			//	we have no choice but to close this window.
1286			if (foundRef) {
1287				TMailWindow* window
1288					= static_cast<TMailApp*>(be_app)->FindWindow(nextRef);
1289				if (window == NULL)
1290					OpenMessage(&nextRef, _CurrentCharacterSet());
1291				else
1292					window->Activate();
1293
1294				SetTrackerSelectionToCurrent();
1295
1296				if (window == NULL)
1297					break;
1298			}
1299
1300			fSent = true;
1301			BMessage msg(B_CLOSE_REQUESTED);
1302			PostMessage(&msg);
1303			break;
1304		}
1305
1306		case M_CLOSE_READ:
1307		{
1308			BMessage message(B_CLOSE_REQUESTED);
1309			message.AddString("status", "Read");
1310			PostMessage(&message);
1311			break;
1312		}
1313		case M_CLOSE_SAVED:
1314		{
1315			BMessage message(B_QUIT_REQUESTED);
1316			message.AddString("status", "Saved");
1317			PostMessage(&message);
1318			break;
1319		}
1320		case kMsgQuitAndKeepAllStatus:
1321			fKeepStatusOnQuit = true;
1322			be_app->PostMessage(B_QUIT_REQUESTED);
1323			break;
1324		case M_CLOSE_CUSTOM:
1325			if (msg->HasString("status")) {
1326				BMessage message(B_CLOSE_REQUESTED);
1327				message.AddString("status", msg->GetString("status"));
1328				PostMessage(&message);
1329			} else {
1330				BRect r = Frame();
1331				r.left += ((r.Width() - STATUS_WIDTH) / 2);
1332				r.right = r.left + STATUS_WIDTH;
1333				r.top += 40;
1334				r.bottom = r.top + STATUS_HEIGHT;
1335
1336				BString string = "could not read";
1337				BNode node(fRef);
1338				if (node.InitCheck() == B_OK)
1339					node.ReadAttrString(B_MAIL_ATTR_STATUS, &string);
1340
1341				new TStatusWindow(r, this, string.String());
1342			}
1343			break;
1344
1345		case M_STATUS:
1346		{
1347			const char* attribute;
1348			if (msg->FindString("attribute", &attribute) != B_OK)
1349				break;
1350
1351			BMessage message(B_CLOSE_REQUESTED);
1352			message.AddString("status", attribute);
1353			PostMessage(&message);
1354			break;
1355		}
1356		case M_HEADER:
1357		{
1358			bool showHeader = !fHeader->IsMarked();
1359			fHeader->SetMarked(showHeader);
1360
1361			BMessage message(M_HEADER);
1362			message.AddBool("header", showHeader);
1363			PostMessage(&message, fContentView->TextView());
1364			break;
1365		}
1366		case M_RAW:
1367		{
1368			bool raw = !(fRaw->IsMarked());
1369			fRaw->SetMarked(raw);
1370			BMessage message(M_RAW);
1371			message.AddBool("raw", raw);
1372			PostMessage(&message, fContentView->TextView());
1373			break;
1374		}
1375		case M_SEND_NOW:
1376		case M_SAVE_AS_DRAFT:
1377			Send(msg->what == M_SEND_NOW);
1378			break;
1379
1380		case M_SAVE:
1381		{
1382			const char* address;
1383			if (msg->FindString("address", (const char**)&address) != B_OK)
1384				break;
1385
1386			BVolumeRoster volumeRoster;
1387			BVolume volume;
1388			BQuery query;
1389			BEntry entry;
1390			bool foundEntry = false;
1391
1392			char* arg = (char*)malloc(strlen("META:email=")
1393				+ strlen(address) + 1);
1394			sprintf(arg, "META:email=%s", address);
1395
1396			// Search a Person file with this email address
1397			while (volumeRoster.GetNextVolume(&volume) == B_NO_ERROR) {
1398				if (!volume.KnowsQuery())
1399					continue;
1400
1401				query.SetVolume(&volume);
1402				query.SetPredicate(arg);
1403				query.Fetch();
1404
1405				if (query.GetNextEntry(&entry) == B_NO_ERROR) {
1406					BMessenger tracker("application/x-vnd.Be-TRAK");
1407					if (tracker.IsValid()) {
1408						entry_ref ref;
1409						entry.GetRef(&ref);
1410
1411						BMessage open(B_REFS_RECEIVED);
1412						open.AddRef("refs", &ref);
1413						tracker.SendMessage(&open);
1414						foundEntry = true;
1415						break;
1416					}
1417				}
1418				// Try next volume, if any
1419				query.Clear();
1420			}
1421
1422			if (!foundEntry) {
1423				// None found.
1424				// Ask to open a new Person file with this address pre-filled
1425
1426				status_t result = be_roster->Launch("application/x-person",
1427					1, &arg);
1428
1429				if (result != B_NO_ERROR) {
1430					BAlert* alert = new BAlert("", B_TRANSLATE(
1431						"Sorry, could not find an application that "
1432						"supports the 'Person' data type."),
1433						B_TRANSLATE("OK"));
1434					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1435					alert->Go();
1436				}
1437			}
1438			free(arg);
1439			break;
1440		}
1441
1442		case M_READ_POS:
1443			PreserveReadingPos(false);
1444			break;
1445
1446		case M_PRINT_SETUP:
1447			PrintSetup();
1448			break;
1449
1450		case M_PRINT:
1451			Print();
1452			break;
1453
1454		case M_SELECT:
1455			break;
1456
1457		case M_FIND:
1458			FindWindow::Find(this);
1459			break;
1460
1461		case M_FIND_AGAIN:
1462			FindWindow::FindAgain(this);
1463			break;
1464
1465		case M_QUOTE:
1466		case M_REMOVE_QUOTE:
1467			PostMessage(msg->what, fContentView);
1468			break;
1469
1470		case M_RANDOM_SIG:
1471		{
1472			BList		sigList;
1473			BMessage	*message;
1474
1475			BVolume volume;
1476			BVolumeRoster().GetBootVolume(&volume);
1477
1478			BQuery query;
1479			query.SetVolume(&volume);
1480
1481			char predicate[128];
1482			sprintf(predicate, "%s = *", INDEX_SIGNATURE);
1483			query.SetPredicate(predicate);
1484			query.Fetch();
1485
1486			BEntry entry;
1487			while (query.GetNextEntry(&entry) == B_NO_ERROR) {
1488				BFile file(&entry, O_RDONLY);
1489				if (file.InitCheck() == B_NO_ERROR) {
1490					entry_ref ref;
1491					entry.GetRef(&ref);
1492
1493					message = new BMessage(M_SIGNATURE);
1494					message->AddRef("ref", &ref);
1495					sigList.AddItem(message);
1496				}
1497			}
1498			if (sigList.CountItems() > 0) {
1499				srand(time(0));
1500				PostMessage((BMessage*)sigList.ItemAt(rand()
1501					% sigList.CountItems()));
1502
1503				for (int32 i = 0; (message = (BMessage*)sigList.ItemAt(i))
1504					!= NULL; i++)
1505					delete message;
1506			}
1507			break;
1508		}
1509		case M_SIGNATURE:
1510		{
1511			BMessage message(*msg);
1512			PostMessage(&message, fContentView);
1513			fSigAdded = true;
1514			break;
1515		}
1516		case M_SIG_MENU:
1517		{
1518			TMenu* menu;
1519			BMenuItem* item;
1520			menu = new TMenu("Add Signature", INDEX_SIGNATURE, M_SIGNATURE,
1521				true);
1522
1523			BPoint where;
1524			if (msg->FindPoint("where", &where) != B_OK) {
1525				BRect bounds = fToolBar->Bounds();
1526				where = fToolBar->ConvertToScreen(BPoint(
1527					(bounds.right - bounds.left) / 2,
1528					(bounds.bottom - bounds.top) / 2));
1529			}
1530
1531			if ((item = menu->Go(where, false, true)) != NULL) {
1532				item->SetTarget(this);
1533				(dynamic_cast<BInvoker*>(item))->Invoke();
1534			}
1535			delete menu;
1536			break;
1537		}
1538
1539		case M_ADD:
1540			if (!fPanel) {
1541				BMessenger me(this);
1542				BMessage msg(REFS_RECEIVED);
1543				fPanel = new BFilePanel(B_OPEN_PANEL, &me, &fOpenFolder, false,
1544					true, &msg);
1545			} else if (!fPanel->Window()->IsHidden()) {
1546				fPanel->Window()->Activate();
1547			}
1548
1549			if (fPanel->Window()->IsHidden())
1550				fPanel->Window()->Show();
1551			break;
1552
1553		case M_REMOVE:
1554			PostMessage(msg->what, fEnclosuresView);
1555			break;
1556
1557		case CHARSET_CHOICE_MADE:
1558		{
1559			int32 charSet;
1560			if (msg->FindInt32("charset", &charSet) != B_OK)
1561				break;
1562
1563			BMessage update(FIELD_CHANGED);
1564			update.AddInt32("bitmask", 0);
1565				// just enable the save button
1566			PostMessage(&update);
1567
1568			if (fIncoming && !fResending) {
1569				// The user wants to see the message they are reading (not
1570				// composing) displayed with a different kind of character set
1571				// for decoding.  Reload the whole message and redisplay.  For
1572				// messages which are being composed, the character set is
1573				// retrieved from the header view when it is needed.
1574
1575				entry_ref fileRef = *fRef;
1576				OpenMessage(&fileRef, charSet);
1577			}
1578			break;
1579		}
1580
1581		case REFS_RECEIVED:
1582			AddEnclosure(msg);
1583			break;
1584
1585		//
1586		//	Navigation Messages
1587		//
1588		case M_UNREAD:
1589			MarkMessageRead(fRef, B_SEEN);
1590			_UpdateReadButton();
1591			PostMessage(M_NEXTMSG);
1592			break;
1593		case M_READ:
1594			wasReadMsg = true;
1595			_UpdateReadButton();
1596			msg->what = M_NEXTMSG;
1597		case M_PREVMSG:
1598		case M_NEXTMSG:
1599		{
1600			if (fRef == NULL)
1601				break;
1602			entry_ref orgRef = *fRef;
1603			entry_ref nextRef = *fRef;
1604			if (GetTrackerWindowFile(&nextRef, (msg->what == M_NEXTMSG))) {
1605				TMailWindow* window = static_cast<TMailApp*>(be_app)
1606					->FindWindow(nextRef);
1607				if (window == NULL) {
1608					BNode node(fRef);
1609					read_flags currentFlag;
1610					if (read_read_attr(node, currentFlag) != B_OK)
1611						currentFlag = B_UNREAD;
1612					if (fAutoMarkRead == true)
1613						MarkMessageRead(fRef, B_READ);
1614					else if (currentFlag != B_READ && !wasReadMsg)
1615						MarkMessageRead(fRef, B_SEEN);
1616
1617					OpenMessage(&nextRef, _CurrentCharacterSet());
1618				} else {
1619					window->Activate();
1620					//fSent = true;
1621					PostMessage(B_CLOSE_REQUESTED);
1622				}
1623
1624				SetTrackerSelectionToCurrent();
1625			} else {
1626				if (wasReadMsg)
1627					PostMessage(B_CLOSE_REQUESTED);
1628
1629				beep();
1630			}
1631			if (wasReadMsg)
1632				MarkMessageRead(&orgRef, B_READ);
1633			break;
1634		}
1635
1636		case M_SAVE_POSITION:
1637			if (fRef != NULL)
1638				SaveTrackerPosition(fRef);
1639			break;
1640
1641		case RESET_BUTTONS:
1642			fChanged = false;
1643			fFieldState = 0;
1644			if (!fHeaderView->IsToEmpty())
1645				fFieldState |= FIELD_TO;
1646			if (!fHeaderView->IsSubjectEmpty())
1647				fFieldState |= FIELD_SUBJECT;
1648			if (!fHeaderView->IsCcEmpty())
1649				fFieldState |= FIELD_CC;
1650			if (!fHeaderView->IsBccEmpty())
1651				fFieldState |= FIELD_BCC;
1652			if (fContentView->TextView()->TextLength() != 0)
1653				fFieldState |= FIELD_BODY;
1654
1655			fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false);
1656			fToolBar->SetActionEnabled(M_PRINT, fFieldState);
1657			fToolBar->SetActionEnabled(M_SEND_NOW, (fFieldState & FIELD_TO)
1658				|| (fFieldState & FIELD_BCC));
1659			break;
1660
1661		case M_CHECK_SPELLING:
1662			if (gDictCount == 0)
1663				// Give the application time to init and load dictionaries.
1664				snooze (1500000);
1665			if (!gDictCount) {
1666				beep();
1667				BAlert* alert = new BAlert("",
1668					B_TRANSLATE("Mail couldn't find its dictionary."),
1669					B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
1670					B_OFFSET_SPACING, B_STOP_ALERT);
1671				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1672				alert->Go();
1673			} else {
1674				fSpelling->SetMarked(!fSpelling->IsMarked());
1675				fContentView->TextView()->EnableSpellCheck(
1676					fSpelling->IsMarked());
1677			}
1678			break;
1679
1680		case M_QUERY_RECIPIENT:
1681		{
1682			BString searchText(fHeaderView->To());
1683			if (searchText != "") {
1684				_LaunchQuery(kSameRecipientItem, B_MAIL_ATTR_TO,
1685					searchText);
1686			}
1687			break;
1688		}
1689
1690		case M_QUERY_SENDER:
1691		{
1692			BString searchText(fHeaderView->From());
1693			if (searchText != "") {
1694				_LaunchQuery(kSameSenderItem, B_MAIL_ATTR_FROM,
1695					searchText);
1696			}
1697			break;
1698		}
1699
1700		case M_QUERY_SUBJECT:
1701		{
1702			// If there's no thread attribute (e.g. new mail) use subject
1703			BString searchText(fHeaderView->Subject());
1704			BNode node(fRef);
1705			if (node.InitCheck() == B_OK)
1706				node.ReadAttrString(B_MAIL_ATTR_THREAD, &searchText);
1707
1708			if (searchText != "") {
1709				// query for subject as sent mails have no thread attribute
1710				_LaunchQuery(kSameSubjectItem, B_MAIL_ATTR_SUBJECT,
1711					searchText);
1712			}
1713			break;
1714		}
1715		case M_EDIT_QUERIES:
1716		{
1717			BPath path;
1718			if (_GetQueryPath(&path) < B_OK)
1719				break;
1720
1721			// the user used this command, make sure the folder actually
1722			// exists - if it didn't inform the user what to do with it
1723			BEntry entry(path.Path());
1724			bool showAlert = false;
1725			if (!entry.Exists()) {
1726				showAlert = true;
1727				create_directory(path.Path(), 0777);
1728			}
1729
1730			BEntry folderEntry;
1731			if (folderEntry.SetTo(path.Path()) == B_OK
1732				&& folderEntry.Exists()) {
1733				BMessage openFolderCommand(B_REFS_RECEIVED);
1734				BMessenger tracker("application/x-vnd.Be-TRAK");
1735
1736				entry_ref ref;
1737				folderEntry.GetRef(&ref);
1738				openFolderCommand.AddRef("refs", &ref);
1739				tracker.SendMessage(&openFolderCommand);
1740			}
1741
1742			if (showAlert) {
1743				// just some patience before Tracker pops up the folder
1744				snooze(250000);
1745				BAlert* alert = new BAlert(B_TRANSLATE("helpful message"),
1746					B_TRANSLATE("Put your favorite e-mail queries and query "
1747					"templates in this folder."), B_TRANSLATE("OK"), NULL, NULL,
1748					B_WIDTH_AS_USUAL, B_IDEA_ALERT);
1749				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1750				alert->Go(NULL);
1751			}
1752
1753			break;
1754		}
1755
1756		case B_PATH_MONITOR:
1757			_RebuildQueryMenu();
1758			break;
1759
1760		default:
1761			BWindow::MessageReceived(msg);
1762	}
1763}
1764
1765
1766void
1767TMailWindow::AddEnclosure(BMessage* msg)
1768{
1769	if (fEnclosuresView == NULL && !fIncoming) {
1770		BRect r;
1771		r.left = 0;
1772		r.top = fHeaderView->Frame().bottom - 1;
1773		r.right = Frame().Width() + 2;
1774		r.bottom = r.top + ENCLOSURES_HEIGHT;
1775
1776		fEnclosuresView = new TEnclosuresView(r, Frame());
1777		AddChild(fEnclosuresView, fContentView);
1778		fContentView->ResizeBy(0, -ENCLOSURES_HEIGHT);
1779		fContentView->MoveBy(0, ENCLOSURES_HEIGHT);
1780	}
1781
1782	if (fEnclosuresView == NULL)
1783		return;
1784
1785	if (msg && msg->HasRef("refs")) {
1786		// Add enclosure to view
1787		PostMessage(msg, fEnclosuresView);
1788
1789		fChanged = true;
1790		BEntry entry;
1791		entry_ref ref;
1792		msg->FindRef("refs", &ref);
1793		entry.SetTo(&ref);
1794		entry.GetParent(&entry);
1795		entry.GetRef(&fOpenFolder);
1796	}
1797}
1798
1799
1800bool
1801TMailWindow::QuitRequested()
1802{
1803	int32 result;
1804
1805	if ((!fIncoming || (fIncoming && fResending)) && fChanged && !fSent
1806		&& (!fHeaderView->IsToEmpty()
1807			|| !fHeaderView->IsSubjectEmpty()
1808			|| !fHeaderView->IsCcEmpty()
1809			|| !fHeaderView->IsBccEmpty()
1810			|| (fContentView->TextView() != NULL
1811				&& strlen(fContentView->TextView()->Text()))
1812			|| (fEnclosuresView != NULL
1813				&& fEnclosuresView->fList->CountItems()))) {
1814		if (fResending) {
1815			BAlert* alert = new BAlert("", B_TRANSLATE(
1816					"Send this message before closing?"),
1817				B_TRANSLATE("Cancel"),
1818				B_TRANSLATE("Don't send"),
1819				B_TRANSLATE("Send"),
1820				B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
1821			alert->SetShortcut(0, B_ESCAPE);
1822			alert->SetShortcut(1, 'd');
1823			alert->SetShortcut(2, 's');
1824			result = alert->Go();
1825
1826			switch (result) {
1827				case 0:	// Cancel
1828					return false;
1829				case 1:	// Don't send
1830					break;
1831				case 2:	// Send
1832					Send(true);
1833					break;
1834			}
1835		} else {
1836			BAlert* alert = new BAlert("",
1837				B_TRANSLATE("Save this message as a draft before closing?"),
1838				B_TRANSLATE("Cancel"),
1839				B_TRANSLATE("Don't save"),
1840				B_TRANSLATE("Save"),
1841				B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
1842			alert->SetShortcut(0, B_ESCAPE);
1843			alert->SetShortcut(1, 'd');
1844			alert->SetShortcut(2, 's');
1845			result = alert->Go();
1846			switch (result) {
1847				case 0:	// Cancel
1848					return false;
1849				case 1:	// Don't Save
1850					break;
1851				case 2:	// Save
1852					Send(false);
1853					break;
1854			}
1855		}
1856	}
1857
1858	BMessage message(WINDOW_CLOSED);
1859	message.AddInt32("kind", MAIL_WINDOW);
1860	message.AddPointer("window", this);
1861	be_app->PostMessage(&message);
1862
1863	if (CurrentMessage() && CurrentMessage()->HasString("status")) {
1864		// User explicitly requests a status to set this message to.
1865		if (!CurrentMessage()->HasString("same")) {
1866			const char* status = CurrentMessage()->FindString("status");
1867			if (status != NULL) {
1868				BNode node(fRef);
1869				if (node.InitCheck() == B_NO_ERROR) {
1870					node.RemoveAttr(B_MAIL_ATTR_STATUS);
1871					WriteAttrString(&node, B_MAIL_ATTR_STATUS, status);
1872				}
1873			}
1874		}
1875	} else if (fRef != NULL && !fKeepStatusOnQuit) {
1876		// ...Otherwise just set the message read
1877		if (fAutoMarkRead == true)
1878			MarkMessageRead(fRef, B_READ);
1879		else {
1880			BNode node(fRef);
1881			read_flags currentFlag;
1882			if (read_read_attr(node, currentFlag) != B_OK)
1883				currentFlag = B_UNREAD;
1884			if (currentFlag == B_UNREAD)
1885				MarkMessageRead(fRef, B_SEEN);
1886		}
1887	}
1888
1889	BPrivate::BPathMonitor::StopWatching(BMessenger(this, this));
1890
1891	return true;
1892}
1893
1894
1895void
1896TMailWindow::Show()
1897{
1898	if (Lock()) {
1899		if (!fResending && (fIncoming || fReplying)) {
1900			fContentView->TextView()->MakeFocus(true);
1901		} else {
1902			fHeaderView->ToControl()->MakeFocus(true);
1903			fHeaderView->ToControl()->SelectAll();
1904		}
1905		Unlock();
1906	}
1907	BWindow::Show();
1908}
1909
1910
1911void
1912TMailWindow::Zoom(BPoint /*pos*/, float /*x*/, float /*y*/)
1913{
1914	float		height;
1915	float		width;
1916
1917	BRect rect = Frame();
1918	width = 80 * fApp->ContentFont().StringWidth("M")
1919		+ (rect.Width() - fContentView->TextView()->Bounds().Width() + 6);
1920
1921	BScreen screen(this);
1922	BRect screenFrame = screen.Frame();
1923	if (width > (screenFrame.Width() - 8))
1924		width = screenFrame.Width() - 8;
1925
1926	height = max_c(fContentView->TextView()->CountLines(), 20)
1927		* fContentView->TextView()->LineHeight(0)
1928		+ (rect.Height() - fContentView->TextView()->Bounds().Height());
1929	if (height > (screenFrame.Height() - 29))
1930		height = screenFrame.Height() - 29;
1931
1932	rect.right = rect.left + width;
1933	rect.bottom = rect.top + height;
1934
1935	if (abs((int)(Frame().Width() - rect.Width())) < 5
1936		&& abs((int)(Frame().Height() - rect.Height())) < 5) {
1937		rect = fZoom;
1938	} else {
1939		fZoom = Frame();
1940		screenFrame.InsetBy(6, 6);
1941
1942		if (rect.Width() > screenFrame.Width())
1943			rect.right = rect.left + screenFrame.Width();
1944		if (rect.Height() > screenFrame.Height())
1945			rect.bottom = rect.top + screenFrame.Height();
1946
1947		if (rect.right > screenFrame.right) {
1948			rect.left -= rect.right - screenFrame.right;
1949			rect.right = screenFrame.right;
1950		}
1951		if (rect.bottom > screenFrame.bottom) {
1952			rect.top -= rect.bottom - screenFrame.bottom;
1953			rect.bottom = screenFrame.bottom;
1954		}
1955		if (rect.left < screenFrame.left) {
1956			rect.right += screenFrame.left - rect.left;
1957			rect.left = screenFrame.left;
1958		}
1959		if (rect.top < screenFrame.top) {
1960			rect.bottom += screenFrame.top - rect.top;
1961			rect.top = screenFrame.top;
1962		}
1963	}
1964
1965	ResizeTo(rect.Width(), rect.Height());
1966	MoveTo(rect.LeftTop());
1967}
1968
1969
1970void
1971TMailWindow::WindowActivated(bool status)
1972{
1973	if (status) {
1974		BAutolock locker(sWindowListLock);
1975		sWindowList.RemoveItem(this);
1976		sWindowList.AddItem(this, 0);
1977	}
1978}
1979
1980
1981void
1982TMailWindow::Forward(entry_ref* ref, TMailWindow* window,
1983	bool includeAttachments)
1984{
1985	BEmailMessage* mail = window->Mail();
1986	if (mail == NULL)
1987		return;
1988
1989	uint32 useAccountFrom = fApp->UseAccountFrom();
1990
1991	fMail = mail->ForwardMessage(useAccountFrom == ACCOUNT_FROM_MAIL,
1992		includeAttachments);
1993
1994	BFile file(ref, O_RDONLY);
1995	if (file.InitCheck() < B_NO_ERROR)
1996		return;
1997
1998	fHeaderView->SetSubject(fMail->Subject());
1999
2000	// set mail account
2001
2002	if (useAccountFrom == ACCOUNT_FROM_MAIL)
2003		fHeaderView->SetAccount(fMail->Account());
2004
2005	if (fMail->CountComponents() > 1) {
2006		// if there are any enclosures to be added, first add the enclosures
2007		// view to the window
2008		AddEnclosure(NULL);
2009		if (fEnclosuresView)
2010			fEnclosuresView->AddEnclosuresFromMail(fMail);
2011	}
2012
2013	fContentView->TextView()->LoadMessage(fMail, false, NULL);
2014	fChanged = false;
2015	fFieldState = 0;
2016}
2017
2018
2019void
2020TMailWindow::Print()
2021{
2022	BPrintJob print(Title());
2023
2024	if (!fApp->HasPrintSettings()) {
2025		if (print.Settings()) {
2026			fApp->SetPrintSettings(print.Settings());
2027		} else {
2028			PrintSetup();
2029			if (!fApp->HasPrintSettings())
2030				return;
2031		}
2032	}
2033
2034	print.SetSettings(new BMessage(fApp->PrintSettings()));
2035
2036	if (print.ConfigJob() == B_OK) {
2037		int32 curPage = 1;
2038		int32 lastLine = 0;
2039		BTextView header_view(print.PrintableRect(), "header",
2040			print.PrintableRect().OffsetByCopy(BPoint(
2041				-print.PrintableRect().left, -print.PrintableRect().top)),
2042			B_FOLLOW_ALL_SIDES);
2043
2044		//---------Init the header fields
2045		#define add_header_field(label, field) { \
2046			/*header_view.SetFontAndColor(be_bold_font);*/ \
2047			header_view.Insert(label); \
2048			header_view.Insert(" "); \
2049			/*header_view.SetFontAndColor(be_plain_font);*/ \
2050			header_view.Insert(field); \
2051			header_view.Insert("\n"); \
2052		}
2053
2054		add_header_field("Subject:", fHeaderView->Subject());
2055		add_header_field("To:", fHeaderView->To());
2056		if (!fHeaderView->IsCcEmpty())
2057			add_header_field(B_TRANSLATE("Cc:"), fHeaderView->Cc());
2058
2059		if (!fHeaderView->IsDateEmpty())
2060			header_view.Insert(fHeaderView->Date());
2061
2062		int32 maxLine = fContentView->TextView()->CountLines();
2063		BRect pageRect = print.PrintableRect();
2064		BRect curPageRect = pageRect;
2065
2066		print.BeginJob();
2067		float header_height = header_view.TextHeight(0,
2068			header_view.CountLines());
2069
2070		BRect rect(0, 0, pageRect.Width(), header_height);
2071		BBitmap bmap(rect, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
2072		bmap.Lock();
2073		bmap.AddChild(&header_view);
2074		print.DrawView(&header_view, rect, BPoint(0.0, 0.0));
2075		HorizontalLine line(BRect(0, 0, pageRect.right, 0));
2076		bmap.AddChild(&line);
2077		print.DrawView(&line, line.Bounds(), BPoint(0, header_height + 1));
2078		bmap.Unlock();
2079		header_height += 5;
2080
2081		do {
2082			int32 lineOffset = fContentView->TextView()->OffsetAt(lastLine);
2083			curPageRect.OffsetTo(0,
2084				fContentView->TextView()->PointAt(lineOffset).y);
2085
2086			int32 fromLine = lastLine;
2087			lastLine = fContentView->TextView()->LineAt(
2088				BPoint(0.0, curPageRect.bottom - ((curPage == 1)
2089					? header_height : 0)));
2090
2091			float curPageHeight = fContentView->TextView()->TextHeight(
2092				fromLine, lastLine) + (curPage == 1 ? header_height : 0);
2093
2094			if (curPageHeight > pageRect.Height()) {
2095				curPageHeight = fContentView->TextView()->TextHeight(
2096					fromLine, --lastLine) + (curPage == 1 ? header_height : 0);
2097			}
2098			curPageRect.bottom = curPageRect.top + curPageHeight - 1.0;
2099
2100			if (curPage >= print.FirstPage() && curPage <= print.LastPage()) {
2101				print.DrawView(fContentView->TextView(), curPageRect,
2102					BPoint(0.0, curPage == 1 ? header_height : 0.0));
2103				print.SpoolPage();
2104			}
2105
2106			curPageRect = pageRect;
2107			lastLine++;
2108			curPage++;
2109
2110		} while (print.CanContinue() && lastLine < maxLine);
2111
2112		print.CommitJob();
2113		bmap.RemoveChild(&header_view);
2114		bmap.RemoveChild(&line);
2115	}
2116}
2117
2118
2119void
2120TMailWindow::PrintSetup()
2121{
2122	BPrintJob printJob("mail_print");
2123
2124	if (fApp->HasPrintSettings()) {
2125		BMessage printSettings = fApp->PrintSettings();
2126		printJob.SetSettings(new BMessage(printSettings));
2127	}
2128
2129	if (printJob.ConfigPage() == B_OK)
2130		fApp->SetPrintSettings(printJob.Settings());
2131}
2132
2133
2134void
2135TMailWindow::SetTo(const char* mailTo, const char* subject, const char* ccTo,
2136	const char* bccTo, const BString* body, BMessage* enclosures)
2137{
2138	Lock();
2139
2140	if (mailTo != NULL && mailTo[0])
2141		fHeaderView->SetTo(mailTo);
2142	if (subject != NULL && subject[0])
2143		fHeaderView->SetSubject(subject);
2144	if (ccTo != NULL && ccTo[0])
2145		fHeaderView->SetCc(ccTo);
2146	if (bccTo != NULL && bccTo[0])
2147		fHeaderView->SetBcc(bccTo);
2148
2149	if (body != NULL && body->Length()) {
2150		fContentView->TextView()->SetText(body->String(), body->Length());
2151		fContentView->TextView()->GoToLine(0);
2152	}
2153
2154	if (enclosures && enclosures->HasRef("refs"))
2155		AddEnclosure(enclosures);
2156
2157	Unlock();
2158}
2159
2160
2161void
2162TMailWindow::CopyMessage(entry_ref* ref, TMailWindow* src)
2163{
2164	BNode file(ref);
2165	if (file.InitCheck() == B_OK) {
2166		BString string;
2167		if (file.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK)
2168			fHeaderView->SetTo(string);
2169
2170		if (file.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK)
2171			fHeaderView->SetSubject(string);
2172
2173		if (file.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK)
2174			fHeaderView->SetCc(string);
2175	}
2176
2177	TTextView* text = src->fContentView->TextView();
2178	text_run_array* style = text->RunArray(0, text->TextLength());
2179
2180	fContentView->TextView()->SetText(text->Text(), text->TextLength(), style);
2181
2182	free(style);
2183}
2184
2185
2186void
2187TMailWindow::Reply(entry_ref* ref, TMailWindow* window, uint32 type)
2188{
2189	fRepliedMail = *ref;
2190	SetOriginatingWindow(window);
2191
2192	BEmailMessage* mail = window->Mail();
2193	if (mail == NULL)
2194		return;
2195
2196	if (type == M_REPLY_ALL)
2197		type = B_MAIL_REPLY_TO_ALL;
2198	else if (type == M_REPLY_TO_SENDER)
2199		type = B_MAIL_REPLY_TO_SENDER;
2200	else
2201		type = B_MAIL_REPLY_TO;
2202
2203	uint32 useAccountFrom = fApp->UseAccountFrom();
2204
2205	fMail = mail->ReplyMessage(mail_reply_to_mode(type),
2206		useAccountFrom == ACCOUNT_FROM_MAIL, QUOTE);
2207
2208	// set header fields
2209	fHeaderView->SetTo(fMail->To());
2210	fHeaderView->SetCc(fMail->CC());
2211	fHeaderView->SetSubject(fMail->Subject());
2212
2213	int32 accountID;
2214	BFile file(window->fRef, B_READ_ONLY);
2215	if (file.ReadAttr("MAIL:reply_with", B_INT32_TYPE, 0, &accountID,
2216		sizeof(int32)) != B_OK)
2217		accountID = -1;
2218
2219	// set mail account
2220
2221	if ((useAccountFrom == ACCOUNT_FROM_MAIL) || (accountID > -1)) {
2222		if (useAccountFrom == ACCOUNT_FROM_MAIL)
2223			fHeaderView->SetAccount(fMail->Account());
2224		else
2225			fHeaderView->SetAccount(accountID);
2226	}
2227
2228	// create preamble string
2229
2230	BString preamble = fApp->ReplyPreamble();
2231
2232	BString name;
2233	mail->GetName(&name);
2234	if (name.Length() <= 0)
2235		name = B_TRANSLATE("(Name unavailable)");
2236
2237	BString address(mail->From());
2238	if (address.Length() <= 0)
2239		address = B_TRANSLATE("(Address unavailable)");
2240
2241	BString date(mail->HeaderField("Date"));
2242	if (date.Length() <= 0)
2243		date = B_TRANSLATE("(Date unavailable)");
2244
2245	preamble.ReplaceAll("%n", name);
2246	preamble.ReplaceAll("%e", address);
2247	preamble.ReplaceAll("%d", date);
2248	preamble.ReplaceAll("\\n", "\n");
2249
2250	// insert (if selection) or load (if whole mail) message text into text view
2251
2252	int32 finish, start;
2253	window->fContentView->TextView()->GetSelection(&start, &finish);
2254	if (start != finish) {
2255		char* text = (char*)malloc(finish - start + 1);
2256		if (text == NULL)
2257			return;
2258
2259		window->fContentView->TextView()->GetText(start, finish - start, text);
2260		if (text[strlen(text) - 1] != '\n') {
2261			text[strlen(text)] = '\n';
2262			finish++;
2263		}
2264		fContentView->TextView()->SetText(text, finish - start);
2265		free(text);
2266
2267		finish = fContentView->TextView()->CountLines();
2268		for (int32 loop = 0; loop < finish; loop++) {
2269			fContentView->TextView()->GoToLine(loop);
2270			fContentView->TextView()->Insert((const char*)QUOTE);
2271		}
2272
2273		if (fApp->ColoredQuotes()) {
2274			const BFont* font = fContentView->TextView()->Font();
2275			int32 length = fContentView->TextView()->TextLength();
2276
2277			TextRunArray style(length / 8 + 8);
2278
2279			FillInQuoteTextRuns(fContentView->TextView(), NULL,
2280				fContentView->TextView()->Text(), length, font, &style.Array(),
2281				style.MaxEntries());
2282
2283			fContentView->TextView()->SetRunArray(0, length, &style.Array());
2284		}
2285
2286		fContentView->TextView()->GoToLine(0);
2287		if (preamble.Length() > 0)
2288			fContentView->TextView()->Insert(preamble);
2289	} else {
2290		fContentView->TextView()->LoadMessage(mail, true, preamble);
2291	}
2292
2293	fReplying = true;
2294}
2295
2296
2297status_t
2298TMailWindow::Send(bool now)
2299{
2300	if (!now) {
2301		status_t status = SaveAsDraft();
2302		if (status != B_OK) {
2303			beep();
2304			BAlert* alert = new BAlert("", B_TRANSLATE("E-mail draft could "
2305				"not be saved!"), B_TRANSLATE("OK"));
2306			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2307			alert->Go();
2308		}
2309		return status;
2310	}
2311
2312	uint32 characterSetToUse = _CurrentCharacterSet();
2313	mail_encoding encodingForBody = quoted_printable;
2314	mail_encoding encodingForHeaders = quoted_printable;
2315
2316	// Set up the encoding to use for converting binary to printable ASCII.
2317	// Normally this will be quoted printable, but for some old software,
2318	// particularly Japanese stuff, they only understand base64.  They also
2319	// prefer it for the smaller size.  Later on this will be reduced to 7bit
2320	// if the encoded text is just 7bit characters.
2321	if (characterSetToUse == B_SJIS_CONVERSION
2322		|| characterSetToUse == B_EUC_CONVERSION)
2323		encodingForBody = base64;
2324	else if (characterSetToUse == B_JIS_CONVERSION
2325		|| characterSetToUse == B_MAIL_US_ASCII_CONVERSION
2326		|| characterSetToUse == B_ISO1_CONVERSION
2327		|| characterSetToUse == B_EUC_KR_CONVERSION)
2328		encodingForBody = eight_bit;
2329
2330	// Using quoted printable headers on almost completely non-ASCII Japanese
2331	// is a waste of time.  Besides, some stupid cell phone services need
2332	// base64 in the headers.
2333	if (characterSetToUse == B_SJIS_CONVERSION
2334		|| characterSetToUse == B_EUC_CONVERSION
2335		|| characterSetToUse == B_JIS_CONVERSION
2336		|| characterSetToUse == B_EUC_KR_CONVERSION)
2337		encodingForHeaders = base64;
2338
2339	// Count the number of characters in the message body which aren't in the
2340	// currently selected character set.  Also see if the resulting encoded
2341	// text can safely use 7 bit characters.
2342	if (fContentView->TextView()->TextLength() > 0) {
2343		// First do a trial encoding with the user's character set.
2344		int32 converterState = 0;
2345		int32 originalLength;
2346		BString tempString;
2347		int32 tempStringLength;
2348		char* tempStringPntr;
2349		originalLength = fContentView->TextView()->TextLength();
2350		tempStringLength = originalLength * 6;
2351			// Some character sets bloat up on escape codes
2352		tempStringPntr = tempString.LockBuffer (tempStringLength);
2353		if (tempStringPntr != NULL && mail_convert_from_utf8(characterSetToUse,
2354				fContentView->TextView()->Text(), &originalLength,
2355				tempStringPntr, &tempStringLength, &converterState,
2356				0x1A /* used for unknown characters */) == B_OK) {
2357			// Check for any characters which don't fit in a 7 bit encoding.
2358			int i;
2359			bool has8Bit = false;
2360			for (i = 0; i < tempStringLength; i++) {
2361				if (tempString[i] == 0 || (tempString[i] & 0x80)) {
2362					has8Bit = true;
2363					break;
2364				}
2365			}
2366			if (!has8Bit)
2367				encodingForBody = seven_bit;
2368			tempString.UnlockBuffer (tempStringLength);
2369
2370			// Count up the number of unencoded characters and warn the user
2371			if (fApp->WarnAboutUnencodableCharacters()) {
2372				// TODO: ideally, the encoding should be silently changed to
2373				// one that can express this character
2374				int32 offset = 0;
2375				int count = 0;
2376				while (offset >= 0) {
2377					offset = tempString.FindFirst (0x1A, offset);
2378					if (offset >= 0) {
2379						count++;
2380						offset++;
2381							// Don't get stuck finding the same character again.
2382					}
2383				}
2384				if (count > 0) {
2385					int32 userAnswer;
2386					BString	messageString;
2387					BString countString;
2388					countString << count;
2389					messageString << B_TRANSLATE("Your main text contains %ld"
2390						" unencodable characters. Perhaps a different "
2391						"character set would work better? Hit Send to send it "
2392						"anyway "
2393						"(a substitute character will be used in place of "
2394						"the unencodable ones), or choose Cancel to go back "
2395						"and try fixing it up.");
2396					messageString.ReplaceFirst("%ld", countString);
2397					BAlert* alert = new BAlert("Question", messageString.String(),
2398						B_TRANSLATE("Send"),
2399						B_TRANSLATE("Cancel"),
2400						NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
2401						B_WARNING_ALERT);
2402					alert->SetShortcut(1, B_ESCAPE);
2403					userAnswer = alert->Go();
2404
2405					if (userAnswer == 1) {
2406						// Cancel was picked.
2407						return -1;
2408					}
2409				}
2410			}
2411		}
2412	}
2413
2414	Hide();
2415		// depending on the system (and I/O) load, this could take a while
2416		// but the user shouldn't be left waiting
2417
2418	status_t result;
2419
2420	if (fResending) {
2421		BFile file(fRef, O_RDONLY);
2422		result = file.InitCheck();
2423		if (result == B_OK) {
2424			BEmailMessage mail(&file);
2425			mail.SetTo(fHeaderView->To(), characterSetToUse,
2426				encodingForHeaders);
2427
2428			if (fHeaderView->AccountID() != ~0L)
2429				mail.SendViaAccount(fHeaderView->AccountID());
2430
2431			result = mail.Send(now);
2432		}
2433	} else {
2434		if (fMail == NULL)
2435			// the mail will be deleted when the window is closed
2436			fMail = new BEmailMessage;
2437
2438		// Had an embarrassing bug where replying to a message and clearing the
2439		// CC field meant that it got sent out anyway, so pass in empty strings
2440		// when changing the header to force it to remove the header.
2441
2442		fMail->SetTo(fHeaderView->To(), characterSetToUse, encodingForHeaders);
2443		fMail->SetSubject(fHeaderView->Subject(), characterSetToUse,
2444			encodingForHeaders);
2445		fMail->SetCC(fHeaderView->Cc(), characterSetToUse, encodingForHeaders);
2446		fMail->SetBCC(fHeaderView->Bcc());
2447
2448		//--- Add X-Mailer field
2449		{
2450			// get app version
2451			version_info info;
2452			memset(&info, 0, sizeof(version_info));
2453
2454			app_info appInfo;
2455			if (be_app->GetAppInfo(&appInfo) == B_OK) {
2456				BFile file(&appInfo.ref, B_READ_ONLY);
2457				if (file.InitCheck() == B_OK) {
2458					BAppFileInfo appFileInfo(&file);
2459					if (appFileInfo.InitCheck() == B_OK)
2460						appFileInfo.GetVersionInfo(&info, B_APP_VERSION_KIND);
2461				}
2462			}
2463
2464			char versionString[255];
2465			sprintf(versionString,
2466				"Mail/Haiku %" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32,
2467				info.major, info.middle, info.minor);
2468			fMail->SetHeaderField("X-Mailer", versionString);
2469		}
2470
2471		/****/
2472
2473		// the content text is always added to make sure there is a mail body
2474		fMail->SetBodyTextTo("");
2475		fContentView->TextView()->AddAsContent(fMail, fApp->WrapMode(),
2476			characterSetToUse, encodingForBody);
2477
2478		if (fEnclosuresView != NULL) {
2479			TListItem* item;
2480			int32 index = 0;
2481			while ((item = (TListItem*)fEnclosuresView->fList->ItemAt(index++))
2482				!= NULL) {
2483				if (item->Component())
2484					continue;
2485
2486				// leave out missing enclosures
2487				BEntry entry(item->Ref());
2488				if (!entry.Exists())
2489					continue;
2490
2491				fMail->Attach(item->Ref(), fApp->AttachAttributes());
2492			}
2493		}
2494		if (fHeaderView->AccountID() != ~0L)
2495			fMail->SendViaAccount(fHeaderView->AccountID());
2496
2497		result = fMail->Send(now);
2498
2499		if (fReplying) {
2500			// Set status of the replied mail
2501
2502			BNode node(&fRepliedMail);
2503			if (node.InitCheck() >= B_OK) {
2504				if (fOriginatingWindow) {
2505					BMessage msg(M_SAVE_POSITION), reply;
2506					fOriginatingWindow->SendMessage(&msg, &reply);
2507				}
2508				WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Replied");
2509			}
2510		}
2511	}
2512
2513	bool close = false;
2514	BString errorMessage;
2515
2516	switch (result) {
2517		case B_OK:
2518			close = true;
2519			fSent = true;
2520
2521			// If it's a draft, remove the draft file
2522			if (fDraft) {
2523				BEntry entry(fRef);
2524				entry.Remove();
2525			}
2526			break;
2527
2528		case B_MAIL_NO_DAEMON:
2529		{
2530			close = true;
2531			fSent = true;
2532
2533			BAlert* alert = new BAlert("no daemon",
2534				B_TRANSLATE("The mail_daemon is not running. The message is "
2535					"queued and will be sent when the mail_daemon is started."),
2536				B_TRANSLATE("Start now"), B_TRANSLATE("OK"));
2537			alert->SetShortcut(1, B_ESCAPE);
2538			int32 start = alert->Go();
2539
2540			if (start == 0) {
2541				BMailDaemon daemon;
2542				result = daemon.Launch();
2543				if (result == B_OK) {
2544					daemon.SendQueuedMail();
2545				} else {
2546					errorMessage
2547						<< B_TRANSLATE("The mail_daemon could not be "
2548							"started:\n\t")
2549						<< strerror(result);
2550				}
2551			}
2552			break;
2553		}
2554
2555//		case B_MAIL_UNKNOWN_HOST:
2556//		case B_MAIL_ACCESS_ERROR:
2557//			sprintf(errorMessage,
2558//				"An error occurred trying to connect with the SMTP "
2559//				"host.  Check your SMTP host name.");
2560//			break;
2561//
2562//		case B_MAIL_NO_RECIPIENT:
2563//			sprintf(errorMessage,
2564//				"You must have either a \"To\" or \"Bcc\" recipient.");
2565//			break;
2566
2567		default:
2568			errorMessage << "An error occurred trying to send mail:\n\t"
2569				<< strerror(result);
2570			break;
2571	}
2572
2573	if (result != B_NO_ERROR && result != B_MAIL_NO_DAEMON) {
2574		beep();
2575		BAlert* alert = new BAlert("", errorMessage.String(),
2576			B_TRANSLATE("OK"));
2577		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2578		alert->Go();
2579	}
2580	if (close) {
2581		PostMessage(B_QUIT_REQUESTED);
2582	} else {
2583		// The window was hidden earlier
2584		Show();
2585	}
2586
2587	return result;
2588}
2589
2590
2591status_t
2592TMailWindow::SaveAsDraft()
2593{
2594	BPath draftPath;
2595	BDirectory dir;
2596	BFile draft;
2597	uint32 flags = 0;
2598
2599	if (fDraft) {
2600		status_t status = draft.SetTo(fRef,
2601				B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
2602		if (status != B_OK)
2603			return status;
2604	} else {
2605		// Get the user home directory
2606		status_t status = find_directory(B_USER_DIRECTORY, &draftPath);
2607		if (status != B_OK)
2608			return status;
2609
2610		// Append the relative path of the draft directory
2611		draftPath.Append(kDraftPath);
2612
2613		// Create the file
2614		status = dir.SetTo(draftPath.Path());
2615		switch (status) {
2616			// Create the directory if it does not exist
2617			case B_ENTRY_NOT_FOUND:
2618				if ((status = dir.CreateDirectory(draftPath.Path(), &dir))
2619					!= B_OK)
2620					return status;
2621			case B_OK:
2622			{
2623				char fileName[B_FILE_NAME_LENGTH];
2624				// save as some version of the message's subject
2625				if (fHeaderView->IsSubjectEmpty()) {
2626					strlcpy(fileName, B_TRANSLATE("Untitled"),
2627						sizeof(fileName));
2628				} else {
2629					strlcpy(fileName, fHeaderView->Subject(), sizeof(fileName));
2630				}
2631
2632				uint32 originalLength = strlen(fileName);
2633
2634				// convert /, \ and : to -
2635				for (char* bad = fileName; (bad = strchr(bad, '/')) != NULL;
2636						++bad) {
2637					*bad = '-';
2638				}
2639				for (char* bad = fileName; (bad = strchr(bad, '\\')) != NULL;
2640						++bad) {
2641					*bad = '-';
2642				}
2643				for (char* bad = fileName; (bad = strchr(bad, ':')) != NULL;
2644						++bad) {
2645					*bad = '-';
2646				}
2647
2648				// Create the file; if the name exists, find a unique name
2649				flags = B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS;
2650				int32 i = 1;
2651				do {
2652					status = draft.SetTo(&dir, fileName, flags);
2653					if (status == B_OK)
2654						break;
2655					char appendix[B_FILE_NAME_LENGTH];
2656					sprintf(appendix, " %" B_PRId32, i++);
2657					int32 pos = min_c(sizeof(fileName) - strlen(appendix),
2658						originalLength);
2659					sprintf(fileName + pos, "%s", appendix);
2660				} while (status == B_FILE_EXISTS);
2661				if (status != B_OK)
2662					return status;
2663
2664				// Cache the ref
2665				if (fRef == NULL)
2666					fRef = new entry_ref;
2667				BEntry entry(&dir, fileName);
2668				entry.GetRef(fRef);
2669				break;
2670			}
2671			default:
2672				return status;
2673		}
2674	}
2675
2676	// Write the content of the message
2677	draft.Write(fContentView->TextView()->Text(),
2678		fContentView->TextView()->TextLength());
2679
2680	// Add the header stuff as attributes
2681	WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->To());
2682	WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->To());
2683	WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->Subject());
2684	if (!fHeaderView->IsCcEmpty())
2685		WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->Cc());
2686	if (!fHeaderView->IsBccEmpty())
2687		WriteAttrString(&draft, B_MAIL_ATTR_BCC, fHeaderView->Bcc());
2688
2689	// Add account
2690	if (fHeaderView->AccountName() != NULL) {
2691		WriteAttrString(&draft, B_MAIL_ATTR_ACCOUNT,
2692			fHeaderView->AccountName());
2693	}
2694
2695	// Add encoding
2696	BMenuItem* menuItem = fEncodingMenu->FindMarked();
2697	if (menuItem != NULL)
2698		WriteAttrString(&draft, "MAIL:encoding", menuItem->Label());
2699
2700	// Add the draft attribute for indexing
2701	uint32 draftAttr = true;
2702	draft.WriteAttr("MAIL:draft", B_INT32_TYPE, 0, &draftAttr, sizeof(uint32));
2703
2704	// Add Attachment paths in attribute
2705	if (fEnclosuresView != NULL) {
2706		TListItem* item;
2707		BString pathStr;
2708
2709		for (int32 i = 0; (item = (TListItem*)fEnclosuresView->fList->ItemAt(i))
2710				!= NULL; i++) {
2711			if (i > 0)
2712				pathStr.Append(":");
2713
2714			BEntry entry(item->Ref(), true);
2715			if (!entry.Exists())
2716				continue;
2717
2718			BPath path;
2719			entry.GetPath(&path);
2720			pathStr.Append(path.Path());
2721		}
2722		if (pathStr.Length())
2723			draft.WriteAttrString("MAIL:attachments", &pathStr);
2724	}
2725
2726	// Set the MIME Type of the file
2727	BNodeInfo info(&draft);
2728	info.SetType(kDraftType);
2729
2730	fDraft = true;
2731	fChanged = false;
2732
2733	fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false);
2734
2735	return B_OK;
2736}
2737
2738
2739status_t
2740TMailWindow::TrainMessageAs(const char* commandWord)
2741{
2742	status_t	errorCode = -1;
2743	BEntry		fileEntry;
2744	BPath		filePath;
2745	BMessage	replyMessage;
2746	BMessage	scriptingMessage;
2747	team_id		serverTeam;
2748
2749	if (fRef == NULL)
2750		goto ErrorExit; // Need to have a real file and name.
2751	errorCode = fileEntry.SetTo(fRef, true);
2752	if (errorCode != B_OK)
2753		goto ErrorExit;
2754	errorCode = fileEntry.GetPath(&filePath);
2755	if (errorCode != B_OK)
2756		goto ErrorExit;
2757	fileEntry.Unset();
2758
2759	// Get a connection to the spam database server.  Launch if needed.
2760
2761	if (!fMessengerToSpamServer.IsValid()) {
2762		// Make sure the server is running.
2763		if (!be_roster->IsRunning (kSpamServerSignature)) {
2764			errorCode = be_roster->Launch (kSpamServerSignature);
2765			if (errorCode != B_OK) {
2766				BPath path;
2767				entry_ref ref;
2768				directory_which places[] = {B_SYSTEM_NONPACKAGED_BIN_DIRECTORY,
2769					B_SYSTEM_BIN_DIRECTORY};
2770				for (int32 i = 0; i < 2; i++) {
2771					find_directory(places[i],&path);
2772					path.Append("spamdbm");
2773					if (!BEntry(path.Path()).Exists())
2774						continue;
2775					get_ref_for_path(path.Path(),&ref);
2776
2777					errorCode = be_roster->Launch(&ref);
2778					if (errorCode == B_OK)
2779						break;
2780				}
2781				if (errorCode != B_OK)
2782					goto ErrorExit;
2783			}
2784		}
2785
2786		// Set up the messenger to the database server.
2787		errorCode = B_SERVER_NOT_FOUND;
2788		serverTeam = be_roster->TeamFor(kSpamServerSignature);
2789		if (serverTeam < 0)
2790			goto ErrorExit;
2791
2792		fMessengerToSpamServer = BMessenger (kSpamServerSignature, serverTeam,
2793			&errorCode);
2794
2795		if (!fMessengerToSpamServer.IsValid())
2796			goto ErrorExit;
2797	}
2798
2799	// Ask the server to train on the message.  Give it the command word and
2800	// the absolute path name to use.
2801
2802	scriptingMessage.MakeEmpty();
2803	scriptingMessage.what = B_SET_PROPERTY;
2804	scriptingMessage.AddSpecifier(commandWord);
2805	errorCode = scriptingMessage.AddData("data", B_STRING_TYPE,
2806		filePath.Path(), strlen(filePath.Path()) + 1, false);
2807	if (errorCode != B_OK)
2808		goto ErrorExit;
2809	replyMessage.MakeEmpty();
2810	errorCode = fMessengerToSpamServer.SendMessage(&scriptingMessage,
2811		&replyMessage);
2812	if (errorCode != B_OK
2813		|| replyMessage.FindInt32("error", &errorCode) != B_OK
2814		|| errorCode != B_OK)
2815		goto ErrorExit; // Classification failed in one of many ways.
2816
2817	SetTitleForMessage();
2818		// Update window title to show new spam classification.
2819	return B_OK;
2820
2821ErrorExit:
2822	beep();
2823	char errorString[1500];
2824	snprintf(errorString, sizeof(errorString), "Unable to train the message "
2825		"file \"%s\" as %s.  Possibly useful error code: %s (%" B_PRId32 ").",
2826		filePath.Path(), commandWord, strerror(errorCode), errorCode);
2827	BAlert* alert = new BAlert("", errorString,	B_TRANSLATE("OK"));
2828	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2829	alert->Go();
2830
2831	return errorCode;
2832}
2833
2834
2835void
2836TMailWindow::SetTitleForMessage()
2837{
2838	// Figure out the title of this message and set the title bar
2839	BString title = B_TRANSLATE_SYSTEM_NAME("Mail");
2840
2841	if (fIncoming) {
2842		if (fMail->GetName(&title) == B_OK)
2843			title << ": \"" << fMail->Subject() << "\"";
2844		else
2845			title = fMail->Subject();
2846
2847		if (fDownloading)
2848			title.Prepend("Downloading: ");
2849
2850		if (fApp->ShowSpamGUI() && fRef != NULL) {
2851			BString	classification;
2852			BNode node(fRef);
2853			char numberString[30];
2854			BString oldTitle(title);
2855			float spamRatio;
2856			if (node.InitCheck() != B_OK || node.ReadAttrString(
2857					"MAIL:classification", &classification) != B_OK)
2858				classification = "Unrated";
2859			if (classification != "Spam" && classification != "Genuine") {
2860				// Uncertain, Unrated and other unknown classes, show the ratio.
2861				if (node.InitCheck() == B_OK && node.ReadAttr("MAIL:ratio_spam",
2862						B_FLOAT_TYPE, 0, &spamRatio, sizeof(spamRatio))
2863							== sizeof(spamRatio)) {
2864					sprintf(numberString, "%.4f", spamRatio);
2865					classification << " " << numberString;
2866				}
2867			}
2868			title = "";
2869			title << "[" << classification << "] " << oldTitle;
2870		}
2871	}
2872	SetTitle(title);
2873}
2874
2875
2876/*!	Open *another* message in the existing mail window.  Some code here is
2877	duplicated from various constructors.
2878	TODO: The duplicated code should be moved to a private initializer method
2879*/
2880status_t
2881TMailWindow::OpenMessage(const entry_ref* ref, uint32 characterSetForDecoding)
2882{
2883	if (ref == NULL)
2884		return B_ERROR;
2885
2886	// Set some references to the email file
2887	delete fRef;
2888	fRef = new entry_ref(*ref);
2889
2890	fPrevTrackerPositionSaved = false;
2891	fNextTrackerPositionSaved = false;
2892
2893	fContentView->TextView()->StopLoad();
2894	delete fMail;
2895	fMail = NULL;
2896
2897	BFile file(fRef, B_READ_ONLY);
2898	status_t err = file.InitCheck();
2899	if (err != B_OK)
2900		return err;
2901
2902	char mimeType[256];
2903	BNodeInfo fileInfo(&file);
2904	fileInfo.GetType(mimeType);
2905
2906	if (strcmp(mimeType, B_PARTIAL_MAIL_TYPE) == 0) {
2907		BMessenger listener(this);
2908		status_t status = BMailDaemon().FetchBody(*ref, &listener);
2909		if (status != B_OK)
2910			fprintf(stderr, "Could not fetch body: %s\n", strerror(status));
2911		fileInfo.GetType(mimeType);
2912		_SetDownloading(true);
2913	} else
2914		_SetDownloading(false);
2915
2916	// Check if it's a draft file, which contains only the text, and has the
2917	// from, to, bcc, attachments listed as attributes.
2918	if (strcmp(kDraftType, mimeType) == 0) {
2919		BNode node(fRef);
2920		off_t size;
2921		BString string;
2922
2923		fMail = new BEmailMessage; // Not really used much, but still needed.
2924
2925		// Load the raw UTF-8 text from the file.
2926		file.GetSize(&size);
2927		fContentView->TextView()->SetText(&file, 0, size);
2928
2929		// Restore Fields from attributes
2930		if (node.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK)
2931			fHeaderView->SetTo(string);
2932		if (node.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK)
2933			fHeaderView->SetSubject(string);
2934		if (node.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK)
2935			fHeaderView->SetCc(string);
2936		if (node.ReadAttrString(B_MAIL_ATTR_BCC, &string) == B_OK)
2937			fHeaderView->SetBcc(string);
2938
2939		// Restore account
2940		if (node.ReadAttrString(B_MAIL_ATTR_ACCOUNT, &string) == B_OK)
2941			fHeaderView->SetAccount(string);
2942
2943		// Restore encoding
2944		if (node.ReadAttrString("MAIL:encoding", &string) == B_OK) {
2945			BMenuItem* encodingItem = fEncodingMenu->FindItem(string.String());
2946			if (encodingItem != NULL)
2947				encodingItem->SetMarked(true);
2948		}
2949
2950		// Restore attachments
2951		if (node.ReadAttrString("MAIL:attachments", &string) == B_OK) {
2952			BMessage msg(REFS_RECEIVED);
2953			entry_ref enc_ref;
2954
2955			char* s = strtok((char*)string.String(), ":");
2956			while (s != NULL) {
2957				BEntry entry(s, true);
2958				if (entry.Exists()) {
2959					entry.GetRef(&enc_ref);
2960					msg.AddRef("refs", &enc_ref);
2961				}
2962				s = strtok(NULL, ":");
2963			}
2964			AddEnclosure(&msg);
2965		}
2966
2967		// restore the reading position if available
2968		PostMessage(M_READ_POS);
2969
2970		PostMessage(RESET_BUTTONS);
2971		fIncoming = false;
2972		fDraft = true;
2973	} else {
2974		// A real mail message, parse its headers to get from, to, etc.
2975		fMail = new BEmailMessage(fRef, characterSetForDecoding);
2976		fIncoming = true;
2977		fHeaderView->SetFromMessage(fMail);
2978	}
2979
2980	err = fMail->InitCheck();
2981	if (err < B_OK) {
2982		delete fMail;
2983		fMail = NULL;
2984		return err;
2985	}
2986
2987	SetTitleForMessage();
2988
2989	if (fIncoming) {
2990		//	Put the addresses in the 'Save Address' Menu
2991		BMenuItem* item;
2992		while ((item = fSaveAddrMenu->RemoveItem((int32)0)) != NULL)
2993			delete item;
2994
2995		// create the list of addresses
2996
2997		BList addressList;
2998		get_address_list(addressList, fMail->To(), extract_address);
2999		get_address_list(addressList, fMail->CC(), extract_address);
3000		get_address_list(addressList, fMail->From(), extract_address);
3001		get_address_list(addressList, fMail->ReplyTo(), extract_address);
3002
3003		BMessage* msg;
3004
3005		for (int32 i = addressList.CountItems(); i-- > 0;) {
3006			char* address = (char*)addressList.RemoveItem((int32)0);
3007
3008			// insert the new address in alphabetical order
3009			int32 index = 0;
3010			while ((item = fSaveAddrMenu->ItemAt(index)) != NULL) {
3011				if (!strcmp(address, item->Label())) {
3012					// item already in list
3013					goto skip;
3014				}
3015
3016				if (strcmp(address, item->Label()) < 0)
3017					break;
3018
3019				index++;
3020			}
3021
3022			msg = new BMessage(M_SAVE);
3023			msg->AddString("address", address);
3024			fSaveAddrMenu->AddItem(new BMenuItem(address, msg), index);
3025
3026		skip:
3027			free(address);
3028		}
3029
3030		// Clear out existing contents of text view.
3031		fContentView->TextView()->SetText("", (int32)0);
3032
3033		fContentView->TextView()->LoadMessage(fMail, false, NULL);
3034
3035		if (fApp->ShowToolBar())
3036			_UpdateReadButton();
3037	}
3038
3039	return B_OK;
3040}
3041
3042
3043TMailWindow*
3044TMailWindow::FrontmostWindow()
3045{
3046	BAutolock locker(sWindowListLock);
3047	if (sWindowList.CountItems() > 0)
3048		return (TMailWindow*)sWindowList.ItemAt(0);
3049
3050	return NULL;
3051}
3052
3053
3054// #pragma mark -
3055
3056
3057status_t
3058TMailWindow::_GetQueryPath(BPath* queryPath) const
3059{
3060	// get the user home directory and from there the query folder
3061	status_t ret = find_directory(B_USER_DIRECTORY, queryPath);
3062	if (ret == B_OK)
3063		ret = queryPath->Append(kQueriesDirectory);
3064
3065	return ret;
3066}
3067
3068
3069void
3070TMailWindow::_RebuildQueryMenu(bool firstTime)
3071{
3072	while (fQueryMenu->ItemAt(0)) {
3073		BMenuItem* item = fQueryMenu->RemoveItem((int32)0);
3074		delete item;
3075	}
3076
3077	fQueryMenu->AddItem(new BMenuItem(kSameRecipientItem,
3078			new BMessage(M_QUERY_RECIPIENT)));
3079	fQueryMenu->AddItem(new BMenuItem(kSameSenderItem,
3080			new BMessage(M_QUERY_SENDER)));
3081	fQueryMenu->AddItem(new BMenuItem(kSameSubjectItem,
3082			new BMessage(M_QUERY_SUBJECT)));
3083
3084	bool queryItemsAdded = false;
3085
3086	BPath queryPath;
3087	if (_GetQueryPath(&queryPath) < B_OK)
3088		return;
3089
3090	BDirectory queryDir(queryPath.Path());
3091
3092	if (firstTime) {
3093		BPrivate::BPathMonitor::StartWatching(queryPath.Path(),
3094			B_WATCH_RECURSIVELY, BMessenger(this, this));
3095	}
3096
3097	// If we find the named query, add it to the menu.
3098	BEntry entry;
3099	while (queryDir.GetNextEntry(&entry) == B_OK) {
3100		char name[B_FILE_NAME_LENGTH + 1];
3101		entry.GetName(name);
3102
3103		char* queryString = _BuildQueryString(&entry);
3104		if (queryString == NULL)
3105			continue;
3106
3107		queryItemsAdded = true;
3108
3109		QueryMenu* queryMenu = new QueryMenu(name, false);
3110		queryMenu->SetTargetForItems(be_app);
3111		queryMenu->SetPredicate(queryString);
3112		fQueryMenu->AddItem(queryMenu);
3113
3114		free(queryString);
3115	}
3116
3117	fQueryMenu->AddItem(new BSeparatorItem());
3118
3119	fQueryMenu->AddItem(new BMenuItem(B_TRANSLATE("Edit queries"
3120			B_UTF8_ELLIPSIS),
3121		new BMessage(M_EDIT_QUERIES), 'E', B_SHIFT_KEY));
3122}
3123
3124
3125char*
3126TMailWindow::_BuildQueryString(BEntry* entry) const
3127{
3128	BNode node(entry);
3129	if (node.InitCheck() != B_OK)
3130		return NULL;
3131
3132	uint32 mode;
3133	if (node.ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, (int32*)&mode,
3134		sizeof(int32)) <= 0) {
3135		mode = kByNameItem;
3136	}
3137
3138	BString queryString;
3139	switch (mode) {
3140		case kByForumlaItem:
3141		{
3142			BString buffer;
3143			if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK)
3144				queryString << buffer;
3145			break;
3146		}
3147
3148		case kByNameItem:
3149		{
3150			BString buffer;
3151			if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK)
3152				queryString << "(name==*" << buffer << "*)";
3153			break;
3154		}
3155
3156		case kByAttributeItem:
3157		{
3158			int32 count = 1;
3159			if (node.ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0,
3160					(int32*)&count, sizeof(int32)) <= 0) {
3161				count = 1;
3162			}
3163
3164			attr_info info;
3165			if (node.GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK)
3166				break;
3167
3168			if (count > 1)
3169				queryString << "(";
3170
3171			char* buffer = new char[info.size];
3172			if (node.ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0,
3173					buffer, (size_t)info.size) == info.size) {
3174				BMessage message;
3175				if (message.Unflatten(buffer) == B_OK) {
3176					for (int32 index = 0; /*index < count*/; index++) {
3177						const char* field;
3178						const char* value;
3179						if (message.FindString("menuSelection", index, &field)
3180								!= B_OK
3181							|| message.FindString("attrViewText", index, &value)
3182								!= B_OK) {
3183							break;
3184						}
3185
3186						// ignore the mime type, we'll force it to be email
3187						// later
3188						if (strcmp(field, "BEOS:TYPE") != 0) {
3189							// TODO: check if subMenu contains the type of
3190							// comparison we are suppose to make here
3191							queryString << "(" << field << "==\""
3192								<< value << "\")";
3193
3194							int32 logicMenuSelectedIndex;
3195							if (message.FindInt32("logicalRelation", index,
3196								&logicMenuSelectedIndex) == B_OK) {
3197								if (logicMenuSelectedIndex == 0)
3198									queryString << "&&";
3199								else if (logicMenuSelectedIndex == 1)
3200									queryString << "||";
3201							} else
3202								break;
3203						}
3204					}
3205				}
3206			}
3207
3208			if (count > 1)
3209				queryString << ")";
3210
3211			delete [] buffer;
3212			break;
3213		}
3214
3215		default:
3216			break;
3217	}
3218
3219	if (queryString.Length() == 0)
3220		return NULL;
3221
3222	// force it to check for email only
3223	if (queryString.FindFirst("text/x-email") < 0) {
3224		BString temp;
3225		temp << "(" << queryString << "&&(BEOS:TYPE==\"text/x-email\"))";
3226		queryString = temp;
3227	}
3228
3229	return strdup(queryString.String());
3230}
3231
3232
3233void
3234TMailWindow::_LaunchQuery(const char* title, const char* attribute,
3235	BString text)
3236{
3237/*	ToDo:
3238	If the search attribute is To or From, it'd be nice to parse the
3239	search text to separate the email address and user name.
3240	Then search for 'name || address' to get all mails of people,
3241	never mind the account or mail config they sent from.
3242*/
3243	text.ReplaceAll(" ", "*"); // query on MAIL:track demands * for space
3244	text.ReplaceAll("\"", "\\\"");
3245
3246	BString* term = new BString("((");
3247	term->Append(attribute);
3248	term->Append("==\"*");
3249	term->Append(text);
3250	term->Append("*\")&&(BEOS:TYPE==\"text/x-email\"))");
3251
3252	BPath queryPath;
3253	if (find_directory(B_USER_CACHE_DIRECTORY, &queryPath) != B_OK)
3254		return;
3255	queryPath.Append("Mail");
3256	if ((create_directory(queryPath.Path(), 0777)) != B_OK)
3257		return;
3258	queryPath.Append(title);
3259	BFile query(queryPath.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
3260	if (query.InitCheck() != B_OK)
3261		return;
3262
3263	BNode queryNode(queryPath.Path());
3264	if (queryNode.InitCheck() != B_OK)
3265		return;
3266
3267	// Copy layout from DefaultQueryTemplates
3268	BPath templatePath;
3269	find_directory(B_USER_SETTINGS_DIRECTORY, &templatePath);
3270	templatePath.Append("Tracker/DefaultQueryTemplates/text_x-email");
3271	BNode templateNode(templatePath.Path());
3272
3273	if (templateNode.InitCheck() == B_OK) {
3274		if (CopyAttributes(templateNode, queryNode) != B_OK) {
3275			syslog(LOG_INFO, "Mail: copying x-email DefaultQueryTemplate "
3276				"attributes failed");
3277		}
3278	}
3279
3280	queryNode.WriteAttrString("_trk/qrystr", term);
3281	BNodeInfo nodeInfo(&queryNode);
3282	nodeInfo.SetType("application/x-vnd.Be-query");
3283
3284	// Launch query
3285	BEntry entry(queryPath.Path());
3286	entry_ref ref;
3287	if (entry.GetRef(&ref) == B_OK)
3288		be_roster->Launch(&ref);
3289}
3290
3291
3292void
3293TMailWindow::_AddReadButton()
3294{
3295	BNode node(fRef);
3296
3297	read_flags flag = B_UNREAD;
3298	read_read_attr(node, flag);
3299
3300	if (flag == B_READ) {
3301		fToolBar->SetActionVisible(M_UNREAD, true);
3302		fToolBar->SetActionVisible(M_READ, false);
3303	} else {
3304		fToolBar->SetActionVisible(M_UNREAD, false);
3305		fToolBar->SetActionVisible(M_READ, true);
3306	}
3307}
3308
3309
3310void
3311TMailWindow::_UpdateReadButton()
3312{
3313	if (fApp->ShowToolBar()) {
3314		if (!fAutoMarkRead && fIncoming)
3315			_AddReadButton();
3316	}
3317	UpdateViews();
3318}
3319
3320
3321void
3322TMailWindow::_UpdateLabel(uint32 command, const char* label, bool show)
3323{
3324	BButton* button = fToolBar->FindButton(command);
3325	if (button != NULL) {
3326		button->SetLabel(show ? label : NULL);
3327		button->SetToolTip(show ? NULL : label);
3328	}
3329}
3330
3331
3332void
3333TMailWindow::_SetDownloading(bool downloading)
3334{
3335	fDownloading = downloading;
3336}
3337
3338
3339uint32
3340TMailWindow::_CurrentCharacterSet() const
3341{
3342	uint32 defaultCharSet = fResending || !fIncoming
3343		? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION;
3344
3345	BMenuItem* marked = fEncodingMenu->FindMarked();
3346	if (marked == NULL)
3347		return defaultCharSet;
3348
3349	return marked->Message()->GetInt32("charset", defaultCharSet);
3350}
3351