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