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