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