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