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("Same recipient");
154static const char* kSameSenderItem = B_TRANSLATE("Same sender");
155static const char* kSameSubjectItem = B_TRANSLATE("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("Increase quote level"),
370			new BMessage(M_ADD_QUOTE_LEVEL), '+');
371		menu->AddItem(fQuote);
372		fRemoveQuote = new BMenuItem(B_TRANSLATE("Decrease quote level"),
373			new BMessage(M_SUB_QUOTE_LEVEL), '-');
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_ADD_QUOTE_LEVEL:
1466		case M_SUB_QUOTE_LEVEL:
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 rect;
1526				BButton* button = fToolBar->FindButton(M_SIG_MENU);
1527				if (button != NULL)
1528					rect = button->Frame();
1529				else
1530					rect = fToolBar->Bounds();
1531
1532				where = button->ConvertToScreen(BPoint(
1533					((rect.right - rect.left) / 2) - 16,
1534					(rect.bottom - rect.top) / 2));
1535			}
1536
1537			if ((item = menu->Go(where, false, true)) != NULL) {
1538				item->SetTarget(this);
1539				(dynamic_cast<BInvoker*>(item))->Invoke();
1540			}
1541			delete menu;
1542			break;
1543		}
1544
1545		case M_ADD:
1546			if (!fPanel) {
1547				BMessenger me(this);
1548				BMessage msg(REFS_RECEIVED);
1549				fPanel = new BFilePanel(B_OPEN_PANEL, &me, &fOpenFolder, false,
1550					true, &msg);
1551			} else if (!fPanel->Window()->IsHidden()) {
1552				fPanel->Window()->Activate();
1553			}
1554
1555			if (fPanel->Window()->IsHidden())
1556				fPanel->Window()->Show();
1557			break;
1558
1559		case M_REMOVE:
1560			PostMessage(msg->what, fEnclosuresView);
1561			break;
1562
1563		case CHARSET_CHOICE_MADE:
1564		{
1565			int32 charSet;
1566			if (msg->FindInt32("charset", &charSet) != B_OK)
1567				break;
1568
1569			BMessage update(FIELD_CHANGED);
1570			update.AddInt32("bitmask", 0);
1571				// just enable the save button
1572			PostMessage(&update);
1573
1574			if (fIncoming && !fResending) {
1575				// The user wants to see the message they are reading (not
1576				// composing) displayed with a different kind of character set
1577				// for decoding.  Reload the whole message and redisplay.  For
1578				// messages which are being composed, the character set is
1579				// retrieved from the header view when it is needed.
1580
1581				entry_ref fileRef = *fRef;
1582				OpenMessage(&fileRef, charSet);
1583			}
1584			break;
1585		}
1586
1587		case REFS_RECEIVED:
1588			AddEnclosure(msg);
1589			break;
1590
1591		//
1592		//	Navigation Messages
1593		//
1594		case M_UNREAD:
1595			MarkMessageRead(fRef, B_SEEN);
1596			_UpdateReadButton();
1597			PostMessage(M_NEXTMSG);
1598			break;
1599		case M_READ:
1600			wasReadMsg = true;
1601			_UpdateReadButton();
1602			msg->what = M_NEXTMSG;
1603		case M_PREVMSG:
1604		case M_NEXTMSG:
1605		{
1606			if (fRef == NULL)
1607				break;
1608			entry_ref orgRef = *fRef;
1609			entry_ref nextRef = *fRef;
1610			if (GetTrackerWindowFile(&nextRef, (msg->what == M_NEXTMSG))) {
1611				TMailWindow* window = static_cast<TMailApp*>(be_app)
1612					->FindWindow(nextRef);
1613				if (window == NULL) {
1614					BNode node(fRef);
1615					read_flags currentFlag;
1616					if (read_read_attr(node, currentFlag) != B_OK)
1617						currentFlag = B_UNREAD;
1618					if (fAutoMarkRead == true)
1619						MarkMessageRead(fRef, B_READ);
1620					else if (currentFlag != B_READ && !wasReadMsg)
1621						MarkMessageRead(fRef, B_SEEN);
1622
1623					OpenMessage(&nextRef, _CurrentCharacterSet());
1624				} else {
1625					window->Activate();
1626					//fSent = true;
1627					PostMessage(B_CLOSE_REQUESTED);
1628				}
1629
1630				SetTrackerSelectionToCurrent();
1631			} else {
1632				if (wasReadMsg)
1633					PostMessage(B_CLOSE_REQUESTED);
1634
1635				beep();
1636			}
1637			if (wasReadMsg)
1638				MarkMessageRead(&orgRef, B_READ);
1639			break;
1640		}
1641
1642		case M_SAVE_POSITION:
1643			if (fRef != NULL)
1644				SaveTrackerPosition(fRef);
1645			break;
1646
1647		case RESET_BUTTONS:
1648			fChanged = false;
1649			fFieldState = 0;
1650			if (!fHeaderView->IsToEmpty())
1651				fFieldState |= FIELD_TO;
1652			if (!fHeaderView->IsSubjectEmpty())
1653				fFieldState |= FIELD_SUBJECT;
1654			if (!fHeaderView->IsCcEmpty())
1655				fFieldState |= FIELD_CC;
1656			if (!fHeaderView->IsBccEmpty())
1657				fFieldState |= FIELD_BCC;
1658			if (fContentView->TextView()->TextLength() != 0)
1659				fFieldState |= FIELD_BODY;
1660
1661			fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false);
1662			fToolBar->SetActionEnabled(M_PRINT, fFieldState);
1663			fToolBar->SetActionEnabled(M_SEND_NOW, (fFieldState & FIELD_TO)
1664				|| (fFieldState & FIELD_BCC));
1665			break;
1666
1667		case M_CHECK_SPELLING:
1668			if (gDictCount == 0)
1669				// Give the application time to init and load dictionaries.
1670				snooze (1500000);
1671			if (!gDictCount) {
1672				beep();
1673				BAlert* alert = new BAlert("",
1674					B_TRANSLATE("Mail couldn't find its dictionary."),
1675					B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
1676					B_OFFSET_SPACING, B_STOP_ALERT);
1677				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1678				alert->Go();
1679			} else {
1680				fSpelling->SetMarked(!fSpelling->IsMarked());
1681				fContentView->TextView()->EnableSpellCheck(
1682					fSpelling->IsMarked());
1683			}
1684			break;
1685
1686		case M_QUERY_RECIPIENT:
1687		{
1688			BString searchText(fHeaderView->To());
1689			if (searchText != "") {
1690				_LaunchQuery(kSameRecipientItem, B_MAIL_ATTR_TO,
1691					searchText);
1692			}
1693			break;
1694		}
1695
1696		case M_QUERY_SENDER:
1697		{
1698			BString searchText(fHeaderView->From());
1699			if (searchText != "") {
1700				_LaunchQuery(kSameSenderItem, B_MAIL_ATTR_FROM,
1701					searchText);
1702			}
1703			break;
1704		}
1705
1706		case M_QUERY_SUBJECT:
1707		{
1708			// If there's no thread attribute (e.g. new mail) use subject
1709			BString searchText(fHeaderView->Subject());
1710			BNode node(fRef);
1711			if (node.InitCheck() == B_OK)
1712				node.ReadAttrString(B_MAIL_ATTR_THREAD, &searchText);
1713
1714			if (searchText != "") {
1715				// query for subject as sent mails have no thread attribute
1716				_LaunchQuery(kSameSubjectItem, B_MAIL_ATTR_SUBJECT,
1717					searchText);
1718			}
1719			break;
1720		}
1721		case M_EDIT_QUERIES:
1722		{
1723			BPath path;
1724			if (_GetQueryPath(&path) < B_OK)
1725				break;
1726
1727			// the user used this command, make sure the folder actually
1728			// exists - if it didn't inform the user what to do with it
1729			BEntry entry(path.Path());
1730			bool showAlert = false;
1731			if (!entry.Exists()) {
1732				showAlert = true;
1733				create_directory(path.Path(), 0777);
1734			}
1735
1736			BEntry folderEntry;
1737			if (folderEntry.SetTo(path.Path()) == B_OK
1738				&& folderEntry.Exists()) {
1739				BMessage openFolderCommand(B_REFS_RECEIVED);
1740				BMessenger tracker("application/x-vnd.Be-TRAK");
1741
1742				entry_ref ref;
1743				folderEntry.GetRef(&ref);
1744				openFolderCommand.AddRef("refs", &ref);
1745				tracker.SendMessage(&openFolderCommand);
1746			}
1747
1748			if (showAlert) {
1749				// just some patience before Tracker pops up the folder
1750				snooze(250000);
1751				BAlert* alert = new BAlert(B_TRANSLATE("helpful message"),
1752					B_TRANSLATE("Put your favorite e-mail queries and query "
1753					"templates in this folder."), B_TRANSLATE("OK"), NULL, NULL,
1754					B_WIDTH_AS_USUAL, B_IDEA_ALERT);
1755				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1756				alert->Go(NULL);
1757			}
1758
1759			break;
1760		}
1761
1762		case B_PATH_MONITOR:
1763			_RebuildQueryMenu();
1764			break;
1765
1766		default:
1767			BWindow::MessageReceived(msg);
1768	}
1769}
1770
1771
1772void
1773TMailWindow::AddEnclosure(BMessage* msg)
1774{
1775	if (fEnclosuresView == NULL && !fIncoming) {
1776		BRect r;
1777		r.left = 0;
1778		r.top = fHeaderView->Frame().bottom - 1;
1779		r.right = Frame().Width() + 2;
1780		r.bottom = r.top + ENCLOSURES_HEIGHT;
1781
1782		fEnclosuresView = new TEnclosuresView(r, Frame());
1783		AddChild(fEnclosuresView, fContentView);
1784		fContentView->ResizeBy(0, -ENCLOSURES_HEIGHT);
1785		fContentView->MoveBy(0, ENCLOSURES_HEIGHT);
1786	}
1787
1788	if (fEnclosuresView == NULL)
1789		return;
1790
1791	if (msg && msg->HasRef("refs")) {
1792		// Add enclosure to view
1793		PostMessage(msg, fEnclosuresView);
1794
1795		fChanged = true;
1796		BEntry entry;
1797		entry_ref ref;
1798		msg->FindRef("refs", &ref);
1799		entry.SetTo(&ref);
1800		entry.GetParent(&entry);
1801		entry.GetRef(&fOpenFolder);
1802	}
1803}
1804
1805
1806bool
1807TMailWindow::QuitRequested()
1808{
1809	int32 result;
1810
1811	if ((!fIncoming || (fIncoming && fResending)) && fChanged && !fSent
1812		&& (!fHeaderView->IsToEmpty()
1813			|| !fHeaderView->IsSubjectEmpty()
1814			|| !fHeaderView->IsCcEmpty()
1815			|| !fHeaderView->IsBccEmpty()
1816			|| (fContentView->TextView() != NULL
1817				&& strlen(fContentView->TextView()->Text()))
1818			|| (fEnclosuresView != NULL
1819				&& fEnclosuresView->fList->CountItems()))) {
1820		if (fResending) {
1821			BAlert* alert = new BAlert("", B_TRANSLATE(
1822					"Send this message before closing?"),
1823				B_TRANSLATE("Cancel"),
1824				B_TRANSLATE("Don't send"),
1825				B_TRANSLATE("Send"),
1826				B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
1827			alert->SetShortcut(0, B_ESCAPE);
1828			alert->SetShortcut(1, 'd');
1829			alert->SetShortcut(2, 's');
1830			result = alert->Go();
1831
1832			switch (result) {
1833				case 0:	// Cancel
1834					return false;
1835				case 1:	// Don't send
1836					break;
1837				case 2:	// Send
1838					Send(true);
1839					break;
1840			}
1841		} else {
1842			BAlert* alert = new BAlert("",
1843				B_TRANSLATE("Save this message as a draft before closing?"),
1844				B_TRANSLATE("Cancel"),
1845				B_TRANSLATE("Don't save"),
1846				B_TRANSLATE("Save"),
1847				B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
1848			alert->SetShortcut(0, B_ESCAPE);
1849			alert->SetShortcut(1, 'd');
1850			alert->SetShortcut(2, 's');
1851			result = alert->Go();
1852			switch (result) {
1853				case 0:	// Cancel
1854					return false;
1855				case 1:	// Don't Save
1856					break;
1857				case 2:	// Save
1858					Send(false);
1859					break;
1860			}
1861		}
1862	}
1863
1864	BMessage message(WINDOW_CLOSED);
1865	message.AddInt32("kind", MAIL_WINDOW);
1866	message.AddPointer("window", this);
1867	be_app->PostMessage(&message);
1868
1869	if (CurrentMessage() && CurrentMessage()->HasString("status")) {
1870		// User explicitly requests a status to set this message to.
1871		if (!CurrentMessage()->HasString("same")) {
1872			const char* status = CurrentMessage()->FindString("status");
1873			if (status != NULL) {
1874				BNode node(fRef);
1875				if (node.InitCheck() == B_NO_ERROR) {
1876					node.RemoveAttr(B_MAIL_ATTR_STATUS);
1877					WriteAttrString(&node, B_MAIL_ATTR_STATUS, status);
1878				}
1879			}
1880		}
1881	} else if (fRef != NULL && !fKeepStatusOnQuit) {
1882		// ...Otherwise just set the message read
1883		if (fAutoMarkRead == true)
1884			MarkMessageRead(fRef, B_READ);
1885		else {
1886			BNode node(fRef);
1887			read_flags currentFlag;
1888			if (read_read_attr(node, currentFlag) != B_OK)
1889				currentFlag = B_UNREAD;
1890			if (currentFlag == B_UNREAD)
1891				MarkMessageRead(fRef, B_SEEN);
1892		}
1893	}
1894
1895	BPrivate::BPathMonitor::StopWatching(BMessenger(this, this));
1896
1897	return true;
1898}
1899
1900
1901void
1902TMailWindow::Show()
1903{
1904	if (Lock()) {
1905		if (!fResending && (fIncoming || fReplying)) {
1906			fContentView->TextView()->MakeFocus(true);
1907		} else {
1908			fHeaderView->ToControl()->MakeFocus(true);
1909			fHeaderView->ToControl()->SelectAll();
1910		}
1911		Unlock();
1912	}
1913	BWindow::Show();
1914}
1915
1916
1917void
1918TMailWindow::Zoom(BPoint /*pos*/, float /*x*/, float /*y*/)
1919{
1920	float		height;
1921	float		width;
1922
1923	BRect rect = Frame();
1924	width = 80 * fApp->ContentFont().StringWidth("M")
1925		+ (rect.Width() - fContentView->TextView()->Bounds().Width() + 6);
1926
1927	BScreen screen(this);
1928	BRect screenFrame = screen.Frame();
1929	if (width > (screenFrame.Width() - 8))
1930		width = screenFrame.Width() - 8;
1931
1932	height = max_c(fContentView->TextView()->CountLines(), 20)
1933		* fContentView->TextView()->LineHeight(0)
1934		+ (rect.Height() - fContentView->TextView()->Bounds().Height());
1935	if (height > (screenFrame.Height() - 29))
1936		height = screenFrame.Height() - 29;
1937
1938	rect.right = rect.left + width;
1939	rect.bottom = rect.top + height;
1940
1941	if (abs((int)(Frame().Width() - rect.Width())) < 5
1942		&& abs((int)(Frame().Height() - rect.Height())) < 5) {
1943		rect = fZoom;
1944	} else {
1945		fZoom = Frame();
1946		screenFrame.InsetBy(6, 6);
1947
1948		if (rect.Width() > screenFrame.Width())
1949			rect.right = rect.left + screenFrame.Width();
1950		if (rect.Height() > screenFrame.Height())
1951			rect.bottom = rect.top + screenFrame.Height();
1952
1953		if (rect.right > screenFrame.right) {
1954			rect.left -= rect.right - screenFrame.right;
1955			rect.right = screenFrame.right;
1956		}
1957		if (rect.bottom > screenFrame.bottom) {
1958			rect.top -= rect.bottom - screenFrame.bottom;
1959			rect.bottom = screenFrame.bottom;
1960		}
1961		if (rect.left < screenFrame.left) {
1962			rect.right += screenFrame.left - rect.left;
1963			rect.left = screenFrame.left;
1964		}
1965		if (rect.top < screenFrame.top) {
1966			rect.bottom += screenFrame.top - rect.top;
1967			rect.top = screenFrame.top;
1968		}
1969	}
1970
1971	ResizeTo(rect.Width(), rect.Height());
1972	MoveTo(rect.LeftTop());
1973}
1974
1975
1976void
1977TMailWindow::WindowActivated(bool status)
1978{
1979	if (status) {
1980		BAutolock locker(sWindowListLock);
1981		sWindowList.RemoveItem(this);
1982		sWindowList.AddItem(this, 0);
1983	}
1984}
1985
1986
1987void
1988TMailWindow::Forward(entry_ref* ref, TMailWindow* window,
1989	bool includeAttachments)
1990{
1991	BEmailMessage* mail = window->Mail();
1992	if (mail == NULL)
1993		return;
1994
1995	uint32 useAccountFrom = fApp->UseAccountFrom();
1996
1997	fMail = mail->ForwardMessage(useAccountFrom == ACCOUNT_FROM_MAIL,
1998		includeAttachments);
1999
2000	BFile file(ref, O_RDONLY);
2001	if (file.InitCheck() < B_NO_ERROR)
2002		return;
2003
2004	fHeaderView->SetSubject(fMail->Subject());
2005
2006	// set mail account
2007
2008	if (useAccountFrom == ACCOUNT_FROM_MAIL)
2009		fHeaderView->SetAccount(fMail->Account());
2010
2011	if (fMail->CountComponents() > 1) {
2012		// if there are any enclosures to be added, first add the enclosures
2013		// view to the window
2014		AddEnclosure(NULL);
2015		if (fEnclosuresView)
2016			fEnclosuresView->AddEnclosuresFromMail(fMail);
2017	}
2018
2019	fContentView->TextView()->LoadMessage(fMail, false, NULL);
2020	fChanged = false;
2021	fFieldState = 0;
2022}
2023
2024
2025void
2026TMailWindow::Print()
2027{
2028	BPrintJob print(Title());
2029
2030	if (!fApp->HasPrintSettings()) {
2031		if (print.Settings()) {
2032			fApp->SetPrintSettings(print.Settings());
2033		} else {
2034			PrintSetup();
2035			if (!fApp->HasPrintSettings())
2036				return;
2037		}
2038	}
2039
2040	print.SetSettings(new BMessage(fApp->PrintSettings()));
2041
2042	if (print.ConfigJob() == B_OK) {
2043		int32 curPage = 1;
2044		int32 lastLine = 0;
2045		BTextView header_view(print.PrintableRect(), "header",
2046			print.PrintableRect().OffsetByCopy(BPoint(
2047				-print.PrintableRect().left, -print.PrintableRect().top)),
2048			B_FOLLOW_ALL_SIDES);
2049
2050		//---------Init the header fields
2051		#define add_header_field(label, field) { \
2052			/*header_view.SetFontAndColor(be_bold_font);*/ \
2053			header_view.Insert(label); \
2054			header_view.Insert(" "); \
2055			/*header_view.SetFontAndColor(be_plain_font);*/ \
2056			header_view.Insert(field); \
2057			header_view.Insert("\n"); \
2058		}
2059
2060		add_header_field("Subject:", fHeaderView->Subject());
2061		add_header_field("To:", fHeaderView->To());
2062		if (!fHeaderView->IsCcEmpty())
2063			add_header_field(B_TRANSLATE("Cc:"), fHeaderView->Cc());
2064
2065		if (!fHeaderView->IsDateEmpty())
2066			header_view.Insert(fHeaderView->Date());
2067
2068		int32 maxLine = fContentView->TextView()->CountLines();
2069		BRect pageRect = print.PrintableRect();
2070		BRect curPageRect = pageRect;
2071
2072		print.BeginJob();
2073		float header_height = header_view.TextHeight(0,
2074			header_view.CountLines());
2075
2076		BRect rect(0, 0, pageRect.Width(), header_height);
2077		BBitmap bmap(rect, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
2078		bmap.Lock();
2079		bmap.AddChild(&header_view);
2080		print.DrawView(&header_view, rect, BPoint(0.0, 0.0));
2081		HorizontalLine line(BRect(0, 0, pageRect.right, 0));
2082		bmap.AddChild(&line);
2083		print.DrawView(&line, line.Bounds(), BPoint(0, header_height + 1));
2084		bmap.Unlock();
2085		header_height += 5;
2086
2087		do {
2088			int32 lineOffset = fContentView->TextView()->OffsetAt(lastLine);
2089			curPageRect.OffsetTo(0,
2090				fContentView->TextView()->PointAt(lineOffset).y);
2091
2092			int32 fromLine = lastLine;
2093			lastLine = fContentView->TextView()->LineAt(
2094				BPoint(0.0, curPageRect.bottom - ((curPage == 1)
2095					? header_height : 0)));
2096
2097			float curPageHeight = fContentView->TextView()->TextHeight(
2098				fromLine, lastLine) + (curPage == 1 ? header_height : 0);
2099
2100			if (curPageHeight > pageRect.Height()) {
2101				curPageHeight = fContentView->TextView()->TextHeight(
2102					fromLine, --lastLine) + (curPage == 1 ? header_height : 0);
2103			}
2104			curPageRect.bottom = curPageRect.top + curPageHeight - 1.0;
2105
2106			if (curPage >= print.FirstPage() && curPage <= print.LastPage()) {
2107				print.DrawView(fContentView->TextView(), curPageRect,
2108					BPoint(0.0, curPage == 1 ? header_height : 0.0));
2109				print.SpoolPage();
2110			}
2111
2112			curPageRect = pageRect;
2113			lastLine++;
2114			curPage++;
2115
2116		} while (print.CanContinue() && lastLine < maxLine);
2117
2118		print.CommitJob();
2119		bmap.RemoveChild(&header_view);
2120		bmap.RemoveChild(&line);
2121	}
2122}
2123
2124
2125void
2126TMailWindow::PrintSetup()
2127{
2128	BPrintJob printJob("mail_print");
2129
2130	if (fApp->HasPrintSettings()) {
2131		BMessage printSettings = fApp->PrintSettings();
2132		printJob.SetSettings(new BMessage(printSettings));
2133	}
2134
2135	if (printJob.ConfigPage() == B_OK)
2136		fApp->SetPrintSettings(printJob.Settings());
2137}
2138
2139
2140void
2141TMailWindow::SetTo(const char* mailTo, const char* subject, const char* ccTo,
2142	const char* bccTo, const BString* body, BMessage* enclosures)
2143{
2144	Lock();
2145
2146	if (mailTo != NULL && mailTo[0])
2147		fHeaderView->SetTo(mailTo);
2148	if (subject != NULL && subject[0])
2149		fHeaderView->SetSubject(subject);
2150	if (ccTo != NULL && ccTo[0])
2151		fHeaderView->SetCc(ccTo);
2152	if (bccTo != NULL && bccTo[0])
2153		fHeaderView->SetBcc(bccTo);
2154
2155	if (body != NULL && body->Length()) {
2156		fContentView->TextView()->SetText(body->String(), body->Length());
2157		fContentView->TextView()->GoToLine(0);
2158	}
2159
2160	if (enclosures && enclosures->HasRef("refs"))
2161		AddEnclosure(enclosures);
2162
2163	Unlock();
2164}
2165
2166
2167void
2168TMailWindow::CopyMessage(entry_ref* ref, TMailWindow* src)
2169{
2170	BNode file(ref);
2171	if (file.InitCheck() == B_OK) {
2172		BString string;
2173		if (file.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK)
2174			fHeaderView->SetTo(string);
2175
2176		if (file.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK)
2177			fHeaderView->SetSubject(string);
2178
2179		if (file.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK)
2180			fHeaderView->SetCc(string);
2181	}
2182
2183	TTextView* text = src->fContentView->TextView();
2184	text_run_array* style = text->RunArray(0, text->TextLength());
2185
2186	fContentView->TextView()->SetText(text->Text(), text->TextLength(), style);
2187
2188	free(style);
2189}
2190
2191
2192void
2193TMailWindow::Reply(entry_ref* ref, TMailWindow* window, uint32 type)
2194{
2195	fRepliedMail = *ref;
2196	SetOriginatingWindow(window);
2197
2198	BEmailMessage* mail = window->Mail();
2199	if (mail == NULL)
2200		return;
2201
2202	if (type == M_REPLY_ALL)
2203		type = B_MAIL_REPLY_TO_ALL;
2204	else if (type == M_REPLY_TO_SENDER)
2205		type = B_MAIL_REPLY_TO_SENDER;
2206	else
2207		type = B_MAIL_REPLY_TO;
2208
2209	uint32 useAccountFrom = fApp->UseAccountFrom();
2210
2211	fMail = mail->ReplyMessage(mail_reply_to_mode(type),
2212		useAccountFrom == ACCOUNT_FROM_MAIL, QUOTE);
2213
2214	// set header fields
2215	fHeaderView->SetTo(fMail->To());
2216	fHeaderView->SetCc(fMail->CC());
2217	fHeaderView->SetSubject(fMail->Subject());
2218
2219	int32 accountID;
2220	BFile file(window->fRef, B_READ_ONLY);
2221	if (file.ReadAttr("MAIL:reply_with", B_INT32_TYPE, 0, &accountID,
2222		sizeof(int32)) != B_OK)
2223		accountID = -1;
2224
2225	// set mail account
2226
2227	if ((useAccountFrom == ACCOUNT_FROM_MAIL) || (accountID > -1)) {
2228		if (useAccountFrom == ACCOUNT_FROM_MAIL)
2229			fHeaderView->SetAccount(fMail->Account());
2230		else
2231			fHeaderView->SetAccount(accountID);
2232	}
2233
2234	// create preamble string
2235
2236	BString preamble = fApp->ReplyPreamble();
2237
2238	BString name;
2239	mail->GetName(&name);
2240	if (name.Length() <= 0)
2241		name = B_TRANSLATE("(Name unavailable)");
2242
2243	BString address(mail->From());
2244	if (address.Length() <= 0)
2245		address = B_TRANSLATE("(Address unavailable)");
2246
2247	BString date(mail->HeaderField("Date"));
2248	if (date.Length() <= 0)
2249		date = B_TRANSLATE("(Date unavailable)");
2250
2251	preamble.ReplaceAll("%n", name);
2252	preamble.ReplaceAll("%e", address);
2253	preamble.ReplaceAll("%d", date);
2254	preamble.ReplaceAll("\\n", "\n");
2255
2256	// insert (if selection) or load (if whole mail) message text into text view
2257
2258	int32 finish, start;
2259	window->fContentView->TextView()->GetSelection(&start, &finish);
2260	if (start != finish) {
2261		char* text = (char*)malloc(finish - start + 1);
2262		if (text == NULL)
2263			return;
2264
2265		window->fContentView->TextView()->GetText(start, finish - start, text);
2266		if (text[strlen(text) - 1] != '\n') {
2267			text[strlen(text)] = '\n';
2268			finish++;
2269		}
2270		fContentView->TextView()->SetText(text, finish - start);
2271		free(text);
2272
2273		finish = fContentView->TextView()->CountLines();
2274		for (int32 loop = 0; loop < finish; loop++) {
2275			fContentView->TextView()->GoToLine(loop);
2276			fContentView->TextView()->Insert((const char*)QUOTE);
2277		}
2278
2279		if (fApp->ColoredQuotes()) {
2280			const BFont* font = fContentView->TextView()->Font();
2281			int32 length = fContentView->TextView()->TextLength();
2282
2283			TextRunArray style(length / 8 + 8);
2284
2285			FillInQuoteTextRuns(fContentView->TextView(), NULL,
2286				fContentView->TextView()->Text(), length, font, &style.Array(),
2287				style.MaxEntries());
2288
2289			fContentView->TextView()->SetRunArray(0, length, &style.Array());
2290		}
2291
2292		fContentView->TextView()->GoToLine(0);
2293		if (preamble.Length() > 0)
2294			fContentView->TextView()->Insert(preamble);
2295	} else {
2296		fContentView->TextView()->LoadMessage(mail, true, preamble);
2297	}
2298
2299	fReplying = true;
2300}
2301
2302
2303status_t
2304TMailWindow::Send(bool now)
2305{
2306	if (!now) {
2307		status_t status = SaveAsDraft();
2308		if (status != B_OK) {
2309			beep();
2310			BAlert* alert = new BAlert("", B_TRANSLATE("E-mail draft could "
2311				"not be saved!"), B_TRANSLATE("OK"));
2312			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2313			alert->Go();
2314		}
2315		return status;
2316	}
2317
2318	uint32 characterSetToUse = _CurrentCharacterSet();
2319	mail_encoding encodingForBody = quoted_printable;
2320	mail_encoding encodingForHeaders = quoted_printable;
2321
2322	// Set up the encoding to use for converting binary to printable ASCII.
2323	// Normally this will be quoted printable, but for some old software,
2324	// particularly Japanese stuff, they only understand base64.  They also
2325	// prefer it for the smaller size.  Later on this will be reduced to 7bit
2326	// if the encoded text is just 7bit characters.
2327	if (characterSetToUse == B_SJIS_CONVERSION
2328		|| characterSetToUse == B_EUC_CONVERSION)
2329		encodingForBody = base64;
2330	else if (characterSetToUse == B_JIS_CONVERSION
2331		|| characterSetToUse == B_MAIL_US_ASCII_CONVERSION
2332		|| characterSetToUse == B_ISO1_CONVERSION
2333		|| characterSetToUse == B_EUC_KR_CONVERSION)
2334		encodingForBody = eight_bit;
2335
2336	// Using quoted printable headers on almost completely non-ASCII Japanese
2337	// is a waste of time.  Besides, some stupid cell phone services need
2338	// base64 in the headers.
2339	if (characterSetToUse == B_SJIS_CONVERSION
2340		|| characterSetToUse == B_EUC_CONVERSION
2341		|| characterSetToUse == B_JIS_CONVERSION
2342		|| characterSetToUse == B_EUC_KR_CONVERSION)
2343		encodingForHeaders = base64;
2344
2345	// Count the number of characters in the message body which aren't in the
2346	// currently selected character set.  Also see if the resulting encoded
2347	// text can safely use 7 bit characters.
2348	if (fContentView->TextView()->TextLength() > 0) {
2349		// First do a trial encoding with the user's character set.
2350		int32 converterState = 0;
2351		int32 originalLength;
2352		BString tempString;
2353		int32 tempStringLength;
2354		char* tempStringPntr;
2355		originalLength = fContentView->TextView()->TextLength();
2356		tempStringLength = originalLength * 6;
2357			// Some character sets bloat up on escape codes
2358		tempStringPntr = tempString.LockBuffer (tempStringLength);
2359		if (tempStringPntr != NULL && mail_convert_from_utf8(characterSetToUse,
2360				fContentView->TextView()->Text(), &originalLength,
2361				tempStringPntr, &tempStringLength, &converterState,
2362				0x1A /* used for unknown characters */) == B_OK) {
2363			// Check for any characters which don't fit in a 7 bit encoding.
2364			int i;
2365			bool has8Bit = false;
2366			for (i = 0; i < tempStringLength; i++) {
2367				if (tempString[i] == 0 || (tempString[i] & 0x80)) {
2368					has8Bit = true;
2369					break;
2370				}
2371			}
2372			if (!has8Bit)
2373				encodingForBody = seven_bit;
2374			tempString.UnlockBuffer (tempStringLength);
2375
2376			// Count up the number of unencoded characters and warn the user
2377			if (fApp->WarnAboutUnencodableCharacters()) {
2378				// TODO: ideally, the encoding should be silently changed to
2379				// one that can express this character
2380				int32 offset = 0;
2381				int count = 0;
2382				while (offset >= 0) {
2383					offset = tempString.FindFirst (0x1A, offset);
2384					if (offset >= 0) {
2385						count++;
2386						offset++;
2387							// Don't get stuck finding the same character again.
2388					}
2389				}
2390				if (count > 0) {
2391					int32 userAnswer;
2392					BString	messageString;
2393					BString countString;
2394					countString << count;
2395					messageString << B_TRANSLATE("Your main text contains %ld"
2396						" unencodable characters. Perhaps a different "
2397						"character set would work better? Hit Send to send it "
2398						"anyway "
2399						"(a substitute character will be used in place of "
2400						"the unencodable ones), or choose Cancel to go back "
2401						"and try fixing it up.");
2402					messageString.ReplaceFirst("%ld", countString);
2403					BAlert* alert = new BAlert("Question", messageString.String(),
2404						B_TRANSLATE("Send"),
2405						B_TRANSLATE("Cancel"),
2406						NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
2407						B_WARNING_ALERT);
2408					alert->SetShortcut(1, B_ESCAPE);
2409					userAnswer = alert->Go();
2410
2411					if (userAnswer == 1) {
2412						// Cancel was picked.
2413						return -1;
2414					}
2415				}
2416			}
2417		}
2418	}
2419
2420	Hide();
2421		// depending on the system (and I/O) load, this could take a while
2422		// but the user shouldn't be left waiting
2423
2424	status_t result;
2425
2426	if (fResending) {
2427		BFile file(fRef, O_RDONLY);
2428		result = file.InitCheck();
2429		if (result == B_OK) {
2430			BEmailMessage mail(&file);
2431			mail.SetTo(fHeaderView->To(), characterSetToUse,
2432				encodingForHeaders);
2433
2434			if (fHeaderView->AccountID() != ~0L)
2435				mail.SendViaAccount(fHeaderView->AccountID());
2436
2437			result = mail.Send(now);
2438		}
2439	} else {
2440		if (fMail == NULL)
2441			// the mail will be deleted when the window is closed
2442			fMail = new BEmailMessage;
2443
2444		// Had an embarrassing bug where replying to a message and clearing the
2445		// CC field meant that it got sent out anyway, so pass in empty strings
2446		// when changing the header to force it to remove the header.
2447
2448		fMail->SetTo(fHeaderView->To(), characterSetToUse, encodingForHeaders);
2449		fMail->SetSubject(fHeaderView->Subject(), characterSetToUse,
2450			encodingForHeaders);
2451		fMail->SetCC(fHeaderView->Cc(), characterSetToUse, encodingForHeaders);
2452		fMail->SetBCC(fHeaderView->Bcc());
2453
2454		//--- Add X-Mailer field
2455		{
2456			// get app version
2457			version_info info;
2458			memset(&info, 0, sizeof(version_info));
2459
2460			app_info appInfo;
2461			if (be_app->GetAppInfo(&appInfo) == B_OK) {
2462				BFile file(&appInfo.ref, B_READ_ONLY);
2463				if (file.InitCheck() == B_OK) {
2464					BAppFileInfo appFileInfo(&file);
2465					if (appFileInfo.InitCheck() == B_OK)
2466						appFileInfo.GetVersionInfo(&info, B_APP_VERSION_KIND);
2467				}
2468			}
2469
2470			char versionString[255];
2471			sprintf(versionString,
2472				"Mail/Haiku %" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32,
2473				info.major, info.middle, info.minor);
2474			fMail->SetHeaderField("X-Mailer", versionString);
2475		}
2476
2477		/****/
2478
2479		// the content text is always added to make sure there is a mail body
2480		fMail->SetBodyTextTo("");
2481		fContentView->TextView()->AddAsContent(fMail, fApp->WrapMode(),
2482			characterSetToUse, encodingForBody);
2483
2484		if (fEnclosuresView != NULL) {
2485			TListItem* item;
2486			int32 index = 0;
2487			while ((item = (TListItem*)fEnclosuresView->fList->ItemAt(index++))
2488				!= NULL) {
2489				if (item->Component())
2490					continue;
2491
2492				// leave out missing enclosures
2493				BEntry entry(item->Ref());
2494				if (!entry.Exists())
2495					continue;
2496
2497				fMail->Attach(item->Ref(), fApp->AttachAttributes());
2498			}
2499		}
2500		if (fHeaderView->AccountID() != ~0L)
2501			fMail->SendViaAccount(fHeaderView->AccountID());
2502
2503		result = fMail->Send(now);
2504
2505		if (fReplying) {
2506			// Set status of the replied mail
2507
2508			BNode node(&fRepliedMail);
2509			if (node.InitCheck() >= B_OK) {
2510				if (fOriginatingWindow) {
2511					BMessage msg(M_SAVE_POSITION), reply;
2512					fOriginatingWindow->SendMessage(&msg, &reply);
2513				}
2514				WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Replied");
2515			}
2516		}
2517	}
2518
2519	bool close = false;
2520	BString errorMessage;
2521
2522	switch (result) {
2523		case B_OK:
2524			close = true;
2525			fSent = true;
2526
2527			// If it's a draft, remove the draft file
2528			if (fDraft) {
2529				BEntry entry(fRef);
2530				entry.Remove();
2531			}
2532			break;
2533
2534		case B_MAIL_NO_DAEMON:
2535		{
2536			close = true;
2537			fSent = true;
2538
2539			BAlert* alert = new BAlert("no daemon",
2540				B_TRANSLATE("The mail_daemon is not running. The message is "
2541					"queued and will be sent when the mail_daemon is started."),
2542				B_TRANSLATE("Start now"), B_TRANSLATE("OK"));
2543			alert->SetShortcut(1, B_ESCAPE);
2544			int32 start = alert->Go();
2545
2546			if (start == 0) {
2547				BMailDaemon daemon;
2548				result = daemon.Launch();
2549				if (result == B_OK) {
2550					daemon.SendQueuedMail();
2551				} else {
2552					errorMessage
2553						<< B_TRANSLATE("The mail_daemon could not be "
2554							"started:\n\t")
2555						<< strerror(result);
2556				}
2557			}
2558			break;
2559		}
2560
2561//		case B_MAIL_UNKNOWN_HOST:
2562//		case B_MAIL_ACCESS_ERROR:
2563//			sprintf(errorMessage,
2564//				"An error occurred trying to connect with the SMTP "
2565//				"host.  Check your SMTP host name.");
2566//			break;
2567//
2568//		case B_MAIL_NO_RECIPIENT:
2569//			sprintf(errorMessage,
2570//				"You must have either a \"To\" or \"Bcc\" recipient.");
2571//			break;
2572
2573		default:
2574			errorMessage << "An error occurred trying to send mail:\n\t"
2575				<< strerror(result);
2576			break;
2577	}
2578
2579	if (result != B_NO_ERROR && result != B_MAIL_NO_DAEMON) {
2580		beep();
2581		BAlert* alert = new BAlert("", errorMessage.String(),
2582			B_TRANSLATE("OK"));
2583		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2584		alert->Go();
2585	}
2586	if (close) {
2587		PostMessage(B_QUIT_REQUESTED);
2588	} else {
2589		// The window was hidden earlier
2590		Show();
2591	}
2592
2593	return result;
2594}
2595
2596
2597status_t
2598TMailWindow::SaveAsDraft()
2599{
2600	BPath draftPath;
2601	BDirectory dir;
2602	BFile draft;
2603	uint32 flags = 0;
2604
2605	if (fDraft) {
2606		status_t status = draft.SetTo(fRef,
2607				B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
2608		if (status != B_OK)
2609			return status;
2610	} else {
2611		// Get the user home directory
2612		status_t status = find_directory(B_USER_DIRECTORY, &draftPath);
2613		if (status != B_OK)
2614			return status;
2615
2616		// Append the relative path of the draft directory
2617		draftPath.Append(kDraftPath);
2618
2619		// Create the file
2620		status = dir.SetTo(draftPath.Path());
2621		switch (status) {
2622			// Create the directory if it does not exist
2623			case B_ENTRY_NOT_FOUND:
2624				if ((status = dir.CreateDirectory(draftPath.Path(), &dir))
2625					!= B_OK)
2626					return status;
2627			case B_OK:
2628			{
2629				char fileName[B_FILE_NAME_LENGTH];
2630				// save as some version of the message's subject
2631				if (fHeaderView->IsSubjectEmpty()) {
2632					strlcpy(fileName, B_TRANSLATE("Untitled"),
2633						sizeof(fileName));
2634				} else {
2635					strlcpy(fileName, fHeaderView->Subject(), sizeof(fileName));
2636				}
2637
2638				uint32 originalLength = strlen(fileName);
2639
2640				// convert /, \ and : to -
2641				for (char* bad = fileName; (bad = strchr(bad, '/')) != NULL;
2642						++bad) {
2643					*bad = '-';
2644				}
2645				for (char* bad = fileName; (bad = strchr(bad, '\\')) != NULL;
2646						++bad) {
2647					*bad = '-';
2648				}
2649				for (char* bad = fileName; (bad = strchr(bad, ':')) != NULL;
2650						++bad) {
2651					*bad = '-';
2652				}
2653
2654				// Create the file; if the name exists, find a unique name
2655				flags = B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS;
2656				int32 i = 1;
2657				do {
2658					status = draft.SetTo(&dir, fileName, flags);
2659					if (status == B_OK)
2660						break;
2661					char appendix[B_FILE_NAME_LENGTH];
2662					sprintf(appendix, " %" B_PRId32, i++);
2663					int32 pos = min_c(sizeof(fileName) - strlen(appendix),
2664						originalLength);
2665					sprintf(fileName + pos, "%s", appendix);
2666				} while (status == B_FILE_EXISTS);
2667				if (status != B_OK)
2668					return status;
2669
2670				// Cache the ref
2671				if (fRef == NULL)
2672					fRef = new entry_ref;
2673				BEntry entry(&dir, fileName);
2674				entry.GetRef(fRef);
2675				break;
2676			}
2677			default:
2678				return status;
2679		}
2680	}
2681
2682	// Write the content of the message
2683	draft.Write(fContentView->TextView()->Text(),
2684		fContentView->TextView()->TextLength());
2685
2686	// Add the header stuff as attributes
2687	WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->To());
2688	WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->To());
2689	WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->Subject());
2690	if (!fHeaderView->IsCcEmpty())
2691		WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->Cc());
2692	if (!fHeaderView->IsBccEmpty())
2693		WriteAttrString(&draft, B_MAIL_ATTR_BCC, fHeaderView->Bcc());
2694
2695	// Add account
2696	if (fHeaderView->AccountName() != NULL) {
2697		WriteAttrString(&draft, B_MAIL_ATTR_ACCOUNT,
2698			fHeaderView->AccountName());
2699	}
2700
2701	// Add encoding
2702	BMenuItem* menuItem = fEncodingMenu->FindMarked();
2703	if (menuItem != NULL)
2704		WriteAttrString(&draft, "MAIL:encoding", menuItem->Label());
2705
2706	// Add the draft attribute for indexing
2707	uint32 draftAttr = true;
2708	draft.WriteAttr("MAIL:draft", B_INT32_TYPE, 0, &draftAttr, sizeof(uint32));
2709
2710	// Add Attachment paths in attribute
2711	if (fEnclosuresView != NULL) {
2712		TListItem* item;
2713		BString pathStr;
2714
2715		for (int32 i = 0; (item = (TListItem*)fEnclosuresView->fList->ItemAt(i))
2716				!= NULL; i++) {
2717			if (i > 0)
2718				pathStr.Append(":");
2719
2720			BEntry entry(item->Ref(), true);
2721			if (!entry.Exists())
2722				continue;
2723
2724			BPath path;
2725			entry.GetPath(&path);
2726			pathStr.Append(path.Path());
2727		}
2728		if (pathStr.Length())
2729			draft.WriteAttrString("MAIL:attachments", &pathStr);
2730	}
2731
2732	// Set the MIME Type of the file
2733	BNodeInfo info(&draft);
2734	info.SetType(kDraftType);
2735
2736	fDraft = true;
2737	fChanged = false;
2738
2739	fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false);
2740
2741	return B_OK;
2742}
2743
2744
2745status_t
2746TMailWindow::TrainMessageAs(const char* commandWord)
2747{
2748	status_t	errorCode = -1;
2749	BEntry		fileEntry;
2750	BPath		filePath;
2751	BMessage	replyMessage;
2752	BMessage	scriptingMessage;
2753	team_id		serverTeam;
2754
2755	if (fRef == NULL)
2756		goto ErrorExit; // Need to have a real file and name.
2757	errorCode = fileEntry.SetTo(fRef, true);
2758	if (errorCode != B_OK)
2759		goto ErrorExit;
2760	errorCode = fileEntry.GetPath(&filePath);
2761	if (errorCode != B_OK)
2762		goto ErrorExit;
2763	fileEntry.Unset();
2764
2765	// Get a connection to the spam database server.  Launch if needed.
2766
2767	if (!fMessengerToSpamServer.IsValid()) {
2768		// Make sure the server is running.
2769		if (!be_roster->IsRunning (kSpamServerSignature)) {
2770			errorCode = be_roster->Launch (kSpamServerSignature);
2771			if (errorCode != B_OK) {
2772				BPath path;
2773				entry_ref ref;
2774				directory_which places[] = {B_SYSTEM_NONPACKAGED_BIN_DIRECTORY,
2775					B_SYSTEM_BIN_DIRECTORY};
2776				for (int32 i = 0; i < 2; i++) {
2777					find_directory(places[i],&path);
2778					path.Append("spamdbm");
2779					if (!BEntry(path.Path()).Exists())
2780						continue;
2781					get_ref_for_path(path.Path(),&ref);
2782
2783					errorCode = be_roster->Launch(&ref);
2784					if (errorCode == B_OK)
2785						break;
2786				}
2787				if (errorCode != B_OK)
2788					goto ErrorExit;
2789			}
2790		}
2791
2792		// Set up the messenger to the database server.
2793		errorCode = B_SERVER_NOT_FOUND;
2794		serverTeam = be_roster->TeamFor(kSpamServerSignature);
2795		if (serverTeam < 0)
2796			goto ErrorExit;
2797
2798		fMessengerToSpamServer = BMessenger (kSpamServerSignature, serverTeam,
2799			&errorCode);
2800
2801		if (!fMessengerToSpamServer.IsValid())
2802			goto ErrorExit;
2803	}
2804
2805	// Ask the server to train on the message.  Give it the command word and
2806	// the absolute path name to use.
2807
2808	scriptingMessage.MakeEmpty();
2809	scriptingMessage.what = B_SET_PROPERTY;
2810	scriptingMessage.AddSpecifier(commandWord);
2811	errorCode = scriptingMessage.AddData("data", B_STRING_TYPE,
2812		filePath.Path(), strlen(filePath.Path()) + 1, false);
2813	if (errorCode != B_OK)
2814		goto ErrorExit;
2815	replyMessage.MakeEmpty();
2816	errorCode = fMessengerToSpamServer.SendMessage(&scriptingMessage,
2817		&replyMessage);
2818	if (errorCode != B_OK
2819		|| replyMessage.FindInt32("error", &errorCode) != B_OK
2820		|| errorCode != B_OK)
2821		goto ErrorExit; // Classification failed in one of many ways.
2822
2823	SetTitleForMessage();
2824		// Update window title to show new spam classification.
2825	return B_OK;
2826
2827ErrorExit:
2828	beep();
2829	char errorString[1500];
2830	snprintf(errorString, sizeof(errorString), "Unable to train the message "
2831		"file \"%s\" as %s.  Possibly useful error code: %s (%" B_PRId32 ").",
2832		filePath.Path(), commandWord, strerror(errorCode), errorCode);
2833	BAlert* alert = new BAlert("", errorString,	B_TRANSLATE("OK"));
2834	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2835	alert->Go();
2836
2837	return errorCode;
2838}
2839
2840
2841void
2842TMailWindow::SetTitleForMessage()
2843{
2844	// Figure out the title of this message and set the title bar
2845	BString title = B_TRANSLATE_SYSTEM_NAME("Mail");
2846
2847	if (fIncoming) {
2848		if (fMail->GetName(&title) == B_OK)
2849			title << ": \"" << fMail->Subject() << "\"";
2850		else
2851			title = fMail->Subject();
2852
2853		if (fDownloading)
2854			title.Prepend("Downloading: ");
2855
2856		if (fApp->ShowSpamGUI() && fRef != NULL) {
2857			BString	classification;
2858			BNode node(fRef);
2859			char numberString[30];
2860			BString oldTitle(title);
2861			float spamRatio;
2862			if (node.InitCheck() != B_OK || node.ReadAttrString(
2863					"MAIL:classification", &classification) != B_OK)
2864				classification = "Unrated";
2865			if (classification != "Spam" && classification != "Genuine") {
2866				// Uncertain, Unrated and other unknown classes, show the ratio.
2867				if (node.InitCheck() == B_OK && node.ReadAttr("MAIL:ratio_spam",
2868						B_FLOAT_TYPE, 0, &spamRatio, sizeof(spamRatio))
2869							== sizeof(spamRatio)) {
2870					sprintf(numberString, "%.4f", spamRatio);
2871					classification << " " << numberString;
2872				}
2873			}
2874			title = "";
2875			title << "[" << classification << "] " << oldTitle;
2876		}
2877	}
2878	SetTitle(title);
2879}
2880
2881
2882/*!	Open *another* message in the existing mail window.  Some code here is
2883	duplicated from various constructors.
2884	TODO: The duplicated code should be moved to a private initializer method
2885*/
2886status_t
2887TMailWindow::OpenMessage(const entry_ref* ref, uint32 characterSetForDecoding)
2888{
2889	if (ref == NULL)
2890		return B_ERROR;
2891
2892	// Set some references to the email file
2893	delete fRef;
2894	fRef = new entry_ref(*ref);
2895
2896	fPrevTrackerPositionSaved = false;
2897	fNextTrackerPositionSaved = false;
2898
2899	fContentView->TextView()->StopLoad();
2900	delete fMail;
2901	fMail = NULL;
2902
2903	BFile file(fRef, B_READ_ONLY);
2904	status_t err = file.InitCheck();
2905	if (err != B_OK)
2906		return err;
2907
2908	char mimeType[256];
2909	BNodeInfo fileInfo(&file);
2910	fileInfo.GetType(mimeType);
2911
2912	if (strcmp(mimeType, B_PARTIAL_MAIL_TYPE) == 0) {
2913		BMessenger listener(this);
2914		status_t status = BMailDaemon().FetchBody(*ref, &listener);
2915		if (status != B_OK)
2916			fprintf(stderr, "Could not fetch body: %s\n", strerror(status));
2917		fileInfo.GetType(mimeType);
2918		_SetDownloading(true);
2919	} else
2920		_SetDownloading(false);
2921
2922	// Check if it's a draft file, which contains only the text, and has the
2923	// from, to, bcc, attachments listed as attributes.
2924	if (strcmp(kDraftType, mimeType) == 0) {
2925		BNode node(fRef);
2926		off_t size;
2927		BString string;
2928
2929		fMail = new BEmailMessage; // Not really used much, but still needed.
2930
2931		// Load the raw UTF-8 text from the file.
2932		file.GetSize(&size);
2933		fContentView->TextView()->SetText(&file, 0, size);
2934
2935		// Restore Fields from attributes
2936		if (node.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK)
2937			fHeaderView->SetTo(string);
2938		if (node.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK)
2939			fHeaderView->SetSubject(string);
2940		if (node.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK)
2941			fHeaderView->SetCc(string);
2942		if (node.ReadAttrString(B_MAIL_ATTR_BCC, &string) == B_OK)
2943			fHeaderView->SetBcc(string);
2944
2945		// Restore account
2946		if (node.ReadAttrString(B_MAIL_ATTR_ACCOUNT, &string) == B_OK)
2947			fHeaderView->SetAccount(string);
2948
2949		// Restore encoding
2950		if (node.ReadAttrString("MAIL:encoding", &string) == B_OK) {
2951			BMenuItem* encodingItem = fEncodingMenu->FindItem(string.String());
2952			if (encodingItem != NULL)
2953				encodingItem->SetMarked(true);
2954		}
2955
2956		// Restore attachments
2957		if (node.ReadAttrString("MAIL:attachments", &string) == B_OK) {
2958			BMessage msg(REFS_RECEIVED);
2959			entry_ref enc_ref;
2960
2961			BStringList list;
2962			string.Split(":", false, list);
2963			for (int32 i = 0; i < list.CountStrings(); i++) {
2964				BEntry entry(list.StringAt(i), true);
2965				if (entry.Exists()) {
2966					entry.GetRef(&enc_ref);
2967					msg.AddRef("refs", &enc_ref);
2968				}
2969			}
2970			AddEnclosure(&msg);
2971		}
2972
2973		// restore the reading position if available
2974		PostMessage(M_READ_POS);
2975
2976		PostMessage(RESET_BUTTONS);
2977		fIncoming = false;
2978		fDraft = true;
2979	} else {
2980		// A real mail message, parse its headers to get from, to, etc.
2981		fMail = new BEmailMessage(fRef, characterSetForDecoding);
2982		fIncoming = true;
2983		fHeaderView->SetFromMessage(fMail);
2984	}
2985
2986	err = fMail->InitCheck();
2987	if (err < B_OK) {
2988		delete fMail;
2989		fMail = NULL;
2990		return err;
2991	}
2992
2993	SetTitleForMessage();
2994
2995	if (fIncoming) {
2996		//	Put the addresses in the 'Save Address' Menu
2997		BMenuItem* item;
2998		while ((item = fSaveAddrMenu->RemoveItem((int32)0)) != NULL)
2999			delete item;
3000
3001		// create the list of addresses
3002
3003		BList addressList;
3004		get_address_list(addressList, fMail->To(), extract_address);
3005		get_address_list(addressList, fMail->CC(), extract_address);
3006		get_address_list(addressList, fMail->From(), extract_address);
3007		get_address_list(addressList, fMail->ReplyTo(), extract_address);
3008
3009		BMessage* msg;
3010
3011		for (int32 i = addressList.CountItems(); i-- > 0;) {
3012			char* address = (char*)addressList.RemoveItem((int32)0);
3013
3014			// insert the new address in alphabetical order
3015			int32 index = 0;
3016			while ((item = fSaveAddrMenu->ItemAt(index)) != NULL) {
3017				if (!strcmp(address, item->Label())) {
3018					// item already in list
3019					goto skip;
3020				}
3021
3022				if (strcmp(address, item->Label()) < 0)
3023					break;
3024
3025				index++;
3026			}
3027
3028			msg = new BMessage(M_SAVE);
3029			msg->AddString("address", address);
3030			fSaveAddrMenu->AddItem(new BMenuItem(address, msg), index);
3031
3032		skip:
3033			free(address);
3034		}
3035
3036		// Clear out existing contents of text view.
3037		fContentView->TextView()->SetText("", (int32)0);
3038
3039		fContentView->TextView()->LoadMessage(fMail, false, NULL);
3040
3041		if (fApp->ShowToolBar())
3042			_UpdateReadButton();
3043	}
3044
3045	return B_OK;
3046}
3047
3048
3049TMailWindow*
3050TMailWindow::FrontmostWindow()
3051{
3052	BAutolock locker(sWindowListLock);
3053	if (sWindowList.CountItems() > 0)
3054		return (TMailWindow*)sWindowList.ItemAt(0);
3055
3056	return NULL;
3057}
3058
3059
3060// #pragma mark -
3061
3062
3063status_t
3064TMailWindow::_GetQueryPath(BPath* queryPath) const
3065{
3066	// get the user home directory and from there the query folder
3067	status_t ret = find_directory(B_USER_DIRECTORY, queryPath);
3068	if (ret == B_OK)
3069		ret = queryPath->Append(kQueriesDirectory);
3070
3071	return ret;
3072}
3073
3074
3075void
3076TMailWindow::_RebuildQueryMenu(bool firstTime)
3077{
3078	while (fQueryMenu->ItemAt(0)) {
3079		BMenuItem* item = fQueryMenu->RemoveItem((int32)0);
3080		delete item;
3081	}
3082
3083	fQueryMenu->AddItem(new BMenuItem(kSameRecipientItem,
3084			new BMessage(M_QUERY_RECIPIENT)));
3085	fQueryMenu->AddItem(new BMenuItem(kSameSenderItem,
3086			new BMessage(M_QUERY_SENDER)));
3087	fQueryMenu->AddItem(new BMenuItem(kSameSubjectItem,
3088			new BMessage(M_QUERY_SUBJECT)));
3089
3090	bool queryItemsAdded = false;
3091
3092	BPath queryPath;
3093	if (_GetQueryPath(&queryPath) < B_OK)
3094		return;
3095
3096	BDirectory queryDir(queryPath.Path());
3097
3098	if (firstTime) {
3099		BPrivate::BPathMonitor::StartWatching(queryPath.Path(),
3100			B_WATCH_RECURSIVELY, BMessenger(this, this));
3101	}
3102
3103	// If we find the named query, add it to the menu.
3104	BEntry entry;
3105	while (queryDir.GetNextEntry(&entry) == B_OK) {
3106		char name[B_FILE_NAME_LENGTH + 1];
3107		entry.GetName(name);
3108
3109		char* queryString = _BuildQueryString(&entry);
3110		if (queryString == NULL)
3111			continue;
3112
3113		queryItemsAdded = true;
3114
3115		QueryMenu* queryMenu = new QueryMenu(name, false);
3116		queryMenu->SetTargetForItems(be_app);
3117		queryMenu->SetPredicate(queryString);
3118		fQueryMenu->AddItem(queryMenu);
3119
3120		free(queryString);
3121	}
3122
3123	fQueryMenu->AddItem(new BSeparatorItem());
3124
3125	fQueryMenu->AddItem(new BMenuItem(B_TRANSLATE("Edit queries"
3126			B_UTF8_ELLIPSIS),
3127		new BMessage(M_EDIT_QUERIES), 'E', B_SHIFT_KEY));
3128}
3129
3130
3131char*
3132TMailWindow::_BuildQueryString(BEntry* entry) const
3133{
3134	BNode node(entry);
3135	if (node.InitCheck() != B_OK)
3136		return NULL;
3137
3138	uint32 mode;
3139	if (node.ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, (int32*)&mode,
3140		sizeof(int32)) <= 0) {
3141		mode = kByNameItem;
3142	}
3143
3144	BString queryString;
3145	switch (mode) {
3146		case kByForumlaItem:
3147		{
3148			BString buffer;
3149			if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK)
3150				queryString << buffer;
3151			break;
3152		}
3153
3154		case kByNameItem:
3155		{
3156			BString buffer;
3157			if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK)
3158				queryString << "(name==*" << buffer << "*)";
3159			break;
3160		}
3161
3162		case kByAttributeItem:
3163		{
3164			int32 count = 1;
3165			if (node.ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0,
3166					(int32*)&count, sizeof(int32)) <= 0) {
3167				count = 1;
3168			}
3169
3170			attr_info info;
3171			if (node.GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK)
3172				break;
3173
3174			if (count > 1)
3175				queryString << "(";
3176
3177			char* buffer = new char[info.size];
3178			if (node.ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0,
3179					buffer, (size_t)info.size) == info.size) {
3180				BMessage message;
3181				if (message.Unflatten(buffer) == B_OK) {
3182					for (int32 index = 0; /*index < count*/; index++) {
3183						const char* field;
3184						const char* value;
3185						if (message.FindString("menuSelection", index, &field)
3186								!= B_OK
3187							|| message.FindString("attrViewText", index, &value)
3188								!= B_OK) {
3189							break;
3190						}
3191
3192						// ignore the mime type, we'll force it to be email
3193						// later
3194						if (strcmp(field, "BEOS:TYPE") != 0) {
3195							// TODO: check if subMenu contains the type of
3196							// comparison we are suppose to make here
3197							queryString << "(" << field << "==\""
3198								<< value << "\")";
3199
3200							int32 logicMenuSelectedIndex;
3201							if (message.FindInt32("logicalRelation", index,
3202								&logicMenuSelectedIndex) == B_OK) {
3203								if (logicMenuSelectedIndex == 0)
3204									queryString << "&&";
3205								else if (logicMenuSelectedIndex == 1)
3206									queryString << "||";
3207							} else
3208								break;
3209						}
3210					}
3211				}
3212			}
3213
3214			if (count > 1)
3215				queryString << ")";
3216
3217			delete [] buffer;
3218			break;
3219		}
3220
3221		default:
3222			break;
3223	}
3224
3225	if (queryString.Length() == 0)
3226		return NULL;
3227
3228	// force it to check for email only
3229	if (queryString.FindFirst("text/x-email") < 0) {
3230		BString temp;
3231		temp << "(" << queryString << "&&(BEOS:TYPE==\"text/x-email\"))";
3232		queryString = temp;
3233	}
3234
3235	return strdup(queryString.String());
3236}
3237
3238
3239void
3240TMailWindow::_LaunchQuery(const char* title, const char* attribute,
3241	BString text)
3242{
3243/*	ToDo:
3244	If the search attribute is To or From, it'd be nice to parse the
3245	search text to separate the email address and user name.
3246	Then search for 'name || address' to get all mails of people,
3247	never mind the account or mail config they sent from.
3248*/
3249	text.ReplaceAll(" ", "*"); // query on MAIL:track demands * for space
3250	text.ReplaceAll("\"", "\\\"");
3251
3252	BString* term = new BString("((");
3253	term->Append(attribute);
3254	term->Append("==\"*");
3255	term->Append(text);
3256	term->Append("*\")&&(BEOS:TYPE==\"text/x-email\"))");
3257
3258	BPath queryPath;
3259	if (find_directory(B_USER_CACHE_DIRECTORY, &queryPath) != B_OK)
3260		return;
3261	queryPath.Append("Mail");
3262	if ((create_directory(queryPath.Path(), 0777)) != B_OK)
3263		return;
3264	queryPath.Append(title);
3265	BFile query(queryPath.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
3266	if (query.InitCheck() != B_OK)
3267		return;
3268
3269	BNode queryNode(queryPath.Path());
3270	if (queryNode.InitCheck() != B_OK)
3271		return;
3272
3273	// Copy layout from DefaultQueryTemplates
3274	BPath templatePath;
3275	find_directory(B_USER_SETTINGS_DIRECTORY, &templatePath);
3276	templatePath.Append("Tracker/DefaultQueryTemplates/text_x-email");
3277	BNode templateNode(templatePath.Path());
3278
3279	if (templateNode.InitCheck() == B_OK) {
3280		if (CopyAttributes(templateNode, queryNode) != B_OK) {
3281			syslog(LOG_INFO, "Mail: copying x-email DefaultQueryTemplate "
3282				"attributes failed");
3283		}
3284	}
3285
3286	queryNode.WriteAttrString("_trk/qrystr", term);
3287	BNodeInfo nodeInfo(&queryNode);
3288	nodeInfo.SetType("application/x-vnd.Be-query");
3289
3290	// Launch query
3291	BEntry entry(queryPath.Path());
3292	entry_ref ref;
3293	if (entry.GetRef(&ref) == B_OK)
3294		be_roster->Launch(&ref);
3295}
3296
3297
3298void
3299TMailWindow::_AddReadButton()
3300{
3301	BNode node(fRef);
3302
3303	read_flags flag = B_UNREAD;
3304	read_read_attr(node, flag);
3305
3306	if (flag == B_READ) {
3307		fToolBar->SetActionVisible(M_UNREAD, true);
3308		fToolBar->SetActionVisible(M_READ, false);
3309	} else {
3310		fToolBar->SetActionVisible(M_UNREAD, false);
3311		fToolBar->SetActionVisible(M_READ, true);
3312	}
3313}
3314
3315
3316void
3317TMailWindow::_UpdateReadButton()
3318{
3319	if (fApp->ShowToolBar()) {
3320		if (!fAutoMarkRead && fIncoming)
3321			_AddReadButton();
3322	}
3323	UpdateViews();
3324}
3325
3326
3327void
3328TMailWindow::_UpdateLabel(uint32 command, const char* label, bool show)
3329{
3330	BButton* button = fToolBar->FindButton(command);
3331	if (button != NULL) {
3332		button->SetLabel(show ? label : NULL);
3333		button->SetToolTip(show ? NULL : label);
3334	}
3335}
3336
3337
3338void
3339TMailWindow::_SetDownloading(bool downloading)
3340{
3341	fDownloading = downloading;
3342}
3343
3344
3345uint32
3346TMailWindow::_CurrentCharacterSet() const
3347{
3348	uint32 defaultCharSet = fResending || !fIncoming
3349		? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION;
3350
3351	BMenuItem* marked = fEncodingMenu->FindMarked();
3352	if (marked == NULL)
3353		return defaultCharSet;
3354
3355	return marked->Message()->GetInt32("charset", defaultCharSet);
3356}
3357