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