xref: /haiku/src/apps/mail/MailWindow.cpp (revision c3cb090f10ebdb7a7ada7b27ff4ec9370fa4dc9c)
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 	BuildButtonBar();
474 
475 	float bbwidth = 0, bbheight = 0;
476 
477 	bool showButtonBar = fApp->ShowButtonBar();
478 
479 	if (showButtonBar) {
480 		fButtonBar->ShowLabels(showButtonBar);
481 		fButtonBar->Arrange(true);
482 		fButtonBar->GetPreferredSize(&bbwidth, &bbheight);
483 		fButtonBar->ResizeTo(Bounds().right, bbheight);
484 		fButtonBar->MoveTo(0, height);
485 		fButtonBar->Show();
486 	}
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 			|| (fContentView->fTextView
1695 				&& strlen(fContentView->fTextView->Text()))
1696 			|| (fEnclosuresView != NULL
1697 				&& fEnclosuresView->fList->CountItems()))) {
1698 		if (fResending) {
1699 			BAlert *alert = new BAlert("", B_TRANSLATE(
1700 					"Send this message before closing?"),
1701 				B_TRANSLATE("Cancel"),
1702 				B_TRANSLATE("Don't send"),
1703 				B_TRANSLATE("Send"),
1704 				B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
1705 			alert->SetShortcut(0, B_ESCAPE);
1706 			alert->SetShortcut(1, 'd');
1707 			alert->SetShortcut(2, 's');
1708 			result = alert->Go();
1709 
1710 			switch (result) {
1711 				case 0:	// Cancel
1712 					return false;
1713 				case 1:	// Don't send
1714 					break;
1715 				case 2:	// Send
1716 					Send(true);
1717 					break;
1718 			}
1719 		} else {
1720 			BAlert *alert = new BAlert("",
1721 				B_TRANSLATE("Save this message as a draft before closing?"),
1722 				B_TRANSLATE("Cancel"),
1723 				B_TRANSLATE("Don't save"),
1724 				B_TRANSLATE("Save"),
1725 				B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
1726 			alert->SetShortcut(0, B_ESCAPE);
1727 			alert->SetShortcut(1, 'd');
1728 			alert->SetShortcut(2, 's');
1729 			result = alert->Go();
1730 			switch (result) {
1731 				case 0:	// Cancel
1732 					return false;
1733 				case 1:	// Don't Save
1734 					break;
1735 				case 2:	// Save
1736 					Send(false);
1737 					break;
1738 			}
1739 		}
1740 	}
1741 
1742 	BMessage message(WINDOW_CLOSED);
1743 	message.AddInt32("kind", MAIL_WINDOW);
1744 	message.AddPointer("window", this);
1745 	be_app->PostMessage(&message);
1746 
1747 	if (CurrentMessage() && CurrentMessage()->HasString("status")) {
1748 		// User explicitly requests a status to set this message to.
1749 		if (!CurrentMessage()->HasString("same")) {
1750 			const char *status = CurrentMessage()->FindString("status");
1751 			if (status != NULL) {
1752 				BNode node(fRef);
1753 				if (node.InitCheck() == B_NO_ERROR) {
1754 					node.RemoveAttr(B_MAIL_ATTR_STATUS);
1755 					WriteAttrString(&node, B_MAIL_ATTR_STATUS, status);
1756 				}
1757 			}
1758 		}
1759 	} else if (fRef != NULL && !fKeepStatusOnQuit) {
1760 		// ...Otherwise just set the message read
1761 		if (fAutoMarkRead == true)
1762 			MarkMessageRead(fRef, B_READ);
1763 		else {
1764 			BNode node(fRef);
1765 			read_flags currentFlag;
1766 			if (read_read_attr(node, currentFlag) != B_OK)
1767 				currentFlag = B_UNREAD;
1768 			if (currentFlag == B_UNREAD)
1769 				MarkMessageRead(fRef, B_SEEN);
1770 		}
1771 	}
1772 
1773 	BPrivate::BPathMonitor::StopWatching(BMessenger(this, this));
1774 
1775 	return true;
1776 }
1777 
1778 
1779 void
1780 TMailWindow::Show()
1781 {
1782 	if (Lock()) {
1783 		if (!fResending && (fIncoming || fReplying)) {
1784 			fContentView->fTextView->MakeFocus(true);
1785 		} else {
1786 			BTextView *textView = fHeaderView->fTo->TextView();
1787 			fHeaderView->fTo->MakeFocus(true);
1788 			textView->Select(0, textView->TextLength());
1789 		}
1790 		Unlock();
1791 	}
1792 	BWindow::Show();
1793 }
1794 
1795 
1796 void
1797 TMailWindow::Zoom(BPoint /*pos*/, float /*x*/, float /*y*/)
1798 {
1799 	float		height;
1800 	float		width;
1801 	BScreen		screen(this);
1802 	BRect		r;
1803 	BRect		s_frame = screen.Frame();
1804 
1805 	r = Frame();
1806 	width = 80 * fApp->ContentFont().StringWidth("M")
1807 		+ (r.Width() - fContentView->fTextView->Bounds().Width() + 6);
1808 	if (width > (s_frame.Width() - 8))
1809 		width = s_frame.Width() - 8;
1810 
1811 	height = max_c(fContentView->fTextView->CountLines(), 20)
1812 		* fContentView->fTextView->LineHeight(0)
1813 		+ (r.Height() - fContentView->fTextView->Bounds().Height());
1814 	if (height > (s_frame.Height() - 29))
1815 		height = s_frame.Height() - 29;
1816 
1817 	r.right = r.left + width;
1818 	r.bottom = r.top + height;
1819 
1820 	if (abs((int)(Frame().Width() - r.Width())) < 5
1821 		&& abs((int)(Frame().Height() - r.Height())) < 5) {
1822 		r = fZoom;
1823 	} else {
1824 		fZoom = Frame();
1825 		s_frame.InsetBy(6, 6);
1826 
1827 		if (r.Width() > s_frame.Width())
1828 			r.right = r.left + s_frame.Width();
1829 		if (r.Height() > s_frame.Height())
1830 			r.bottom = r.top + s_frame.Height();
1831 
1832 		if (r.right > s_frame.right)
1833 		{
1834 			r.left -= r.right - s_frame.right;
1835 			r.right = s_frame.right;
1836 		}
1837 		if (r.bottom > s_frame.bottom)
1838 		{
1839 			r.top -= r.bottom - s_frame.bottom;
1840 			r.bottom = s_frame.bottom;
1841 		}
1842 		if (r.left < s_frame.left)
1843 		{
1844 			r.right += s_frame.left - r.left;
1845 			r.left = s_frame.left;
1846 		}
1847 		if (r.top < s_frame.top)
1848 		{
1849 			r.bottom += s_frame.top - r.top;
1850 			r.top = s_frame.top;
1851 		}
1852 	}
1853 
1854 	ResizeTo(r.Width(), r.Height());
1855 	MoveTo(r.LeftTop());
1856 }
1857 
1858 
1859 void
1860 TMailWindow::WindowActivated(bool status)
1861 {
1862 	if (status) {
1863 		BAutolock locker(sWindowListLock);
1864 		sWindowList.RemoveItem(this);
1865 		sWindowList.AddItem(this, 0);
1866 	}
1867 }
1868 
1869 
1870 void
1871 TMailWindow::Forward(entry_ref *ref, TMailWindow *window,
1872 	bool includeAttachments)
1873 {
1874 	BEmailMessage *mail = window->Mail();
1875 	if (mail == NULL)
1876 		return;
1877 
1878 	uint32 useAccountFrom = fApp->UseAccountFrom();
1879 
1880 	fMail = mail->ForwardMessage(useAccountFrom == ACCOUNT_FROM_MAIL,
1881 		includeAttachments);
1882 
1883 	BFile file(ref, O_RDONLY);
1884 	if (file.InitCheck() < B_NO_ERROR)
1885 		return;
1886 
1887 	fHeaderView->fSubject->SetText(fMail->Subject());
1888 
1889 	// set mail account
1890 
1891 	if (useAccountFrom == ACCOUNT_FROM_MAIL) {
1892 		fHeaderView->fAccountID = fMail->Account();
1893 
1894 		BMenu *menu = fHeaderView->fAccountMenu;
1895 		for (int32 i = menu->CountItems(); i-- > 0;) {
1896 			BMenuItem *item = menu->ItemAt(i);
1897 			BMessage *msg;
1898 			if (item && (msg = item->Message()) != NULL
1899 				&& msg->FindInt32("id") == fHeaderView->fAccountID)
1900 				item->SetMarked(true);
1901 		}
1902 	}
1903 
1904 	if (fMail->CountComponents() > 1) {
1905 		// if there are any enclosures to be added, first add the enclosures
1906 		// view to the window
1907 		AddEnclosure(NULL);
1908 		if (fEnclosuresView)
1909 			fEnclosuresView->AddEnclosuresFromMail(fMail);
1910 	}
1911 
1912 	fContentView->fTextView->LoadMessage(fMail, false, NULL);
1913 	fChanged = false;
1914 	fFieldState = 0;
1915 }
1916 
1917 
1918 class HorizontalLine : public BView {
1919 	public:
1920 		HorizontalLine(BRect rect)
1921 			:
1922 			BView (rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW) {}
1923 
1924 		virtual void Draw(BRect rect)
1925 		{
1926 			FillRect(rect, B_SOLID_HIGH);
1927 		}
1928 };
1929 
1930 
1931 void
1932 TMailWindow::Print()
1933 {
1934 	BPrintJob print(Title());
1935 
1936 	if (!fApp->HasPrintSettings()) {
1937 		if (print.Settings()) {
1938 			fApp->SetPrintSettings(print.Settings());
1939 		} else {
1940 			PrintSetup();
1941 			if (!fApp->HasPrintSettings())
1942 				return;
1943 		}
1944 	}
1945 
1946 	print.SetSettings(new BMessage(fApp->PrintSettings()));
1947 
1948 	if (print.ConfigJob() == B_OK) {
1949 		int32 curPage = 1;
1950 		int32 lastLine = 0;
1951 		BTextView header_view(print.PrintableRect(), "header",
1952 			print.PrintableRect().OffsetByCopy(BPoint(
1953 				-print.PrintableRect().left, -print.PrintableRect().top)),
1954 			B_FOLLOW_ALL_SIDES);
1955 
1956 		//---------Init the header fields
1957 		#define add_header_field(field) { \
1958 			/*header_view.SetFontAndColor(be_bold_font);*/ \
1959 			header_view.Insert(fHeaderView->field->Label()); \
1960 			header_view.Insert(" "); \
1961 			/*header_view.SetFontAndColor(be_plain_font);*/ \
1962 			header_view.Insert(fHeaderView->field->Text()); \
1963 			header_view.Insert("\n"); \
1964 		}
1965 
1966 		add_header_field(fSubject);
1967 		add_header_field(fTo);
1968 		if ((fHeaderView->fCc != NULL)
1969 			&& (strcmp(fHeaderView->fCc->Text(),"") != 0))
1970 			add_header_field(fCc);
1971 
1972 		if (fHeaderView->fDate != NULL)
1973 			header_view.Insert(fHeaderView->fDate->Text());
1974 
1975 		int32 maxLine = fContentView->fTextView->CountLines();
1976 		BRect pageRect = print.PrintableRect();
1977 		BRect curPageRect = pageRect;
1978 
1979 		print.BeginJob();
1980 		float header_height = header_view.TextHeight(0,
1981 			header_view.CountLines());
1982 
1983 		BRect rect(0, 0, pageRect.Width(), header_height);
1984 		BBitmap bmap(rect, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
1985 		bmap.Lock();
1986 		bmap.AddChild(&header_view);
1987 		print.DrawView(&header_view, rect, BPoint(0.0, 0.0));
1988 		HorizontalLine line(BRect(0, 0, pageRect.right, 0));
1989 		bmap.AddChild(&line);
1990 		print.DrawView(&line, line.Bounds(), BPoint(0, header_height + 1));
1991 		bmap.Unlock();
1992 		header_height += 5;
1993 
1994 		do {
1995 			int32 lineOffset = fContentView->fTextView->OffsetAt(lastLine);
1996 			curPageRect.OffsetTo(0,
1997 				fContentView->fTextView->PointAt(lineOffset).y);
1998 
1999 			int32 fromLine = lastLine;
2000 			lastLine = fContentView->fTextView->LineAt(
2001 				BPoint(0.0, curPageRect.bottom - ((curPage == 1)
2002 					? header_height : 0)));
2003 
2004 			float curPageHeight = fContentView->fTextView->
2005 				TextHeight(fromLine, lastLine) + ((curPage == 1)
2006 					? header_height : 0);
2007 
2008 			if (curPageHeight > pageRect.Height()) {
2009 				curPageHeight = fContentView->fTextView->TextHeight(
2010 					fromLine, --lastLine) + ((curPage == 1)
2011 						? header_height : 0);
2012 			}
2013 			curPageRect.bottom = curPageRect.top + curPageHeight - 1.0;
2014 
2015 			if ((curPage >= print.FirstPage())
2016 				&& (curPage <= print.LastPage())) {
2017 				print.DrawView(fContentView->fTextView, curPageRect,
2018 					BPoint(0.0, (curPage == 1) ? header_height : 0.0));
2019 				print.SpoolPage();
2020 			}
2021 
2022 			curPageRect = pageRect;
2023 			lastLine++;
2024 			curPage++;
2025 
2026 		} while (print.CanContinue() && lastLine < maxLine);
2027 
2028 		print.CommitJob();
2029 		bmap.RemoveChild(&header_view);
2030 		bmap.RemoveChild(&line);
2031 	}
2032 }
2033 
2034 
2035 void
2036 TMailWindow::PrintSetup()
2037 {
2038 	BPrintJob printJob("mail_print");
2039 
2040 	if (fApp->HasPrintSettings()) {
2041 		BMessage printSettings = fApp->PrintSettings();
2042 		printJob.SetSettings(new BMessage(printSettings));
2043 	}
2044 
2045 	if (printJob.ConfigPage() == B_OK)
2046 		fApp->SetPrintSettings(printJob.Settings());
2047 }
2048 
2049 
2050 void
2051 TMailWindow::SetTo(const char *mailTo, const char *subject, const char *ccTo,
2052 	const char *bccTo, const BString *body, BMessage *enclosures)
2053 {
2054 	Lock();
2055 
2056 	if (mailTo && mailTo[0])
2057 		fHeaderView->fTo->SetText(mailTo);
2058 	if (subject && subject[0])
2059 		fHeaderView->fSubject->SetText(subject);
2060 	if (ccTo && ccTo[0])
2061 		fHeaderView->fCc->SetText(ccTo);
2062 	if (bccTo && bccTo[0])
2063 		fHeaderView->fBcc->SetText(bccTo);
2064 
2065 	if (body && body->Length()) {
2066 		fContentView->fTextView->SetText(body->String(), body->Length());
2067 		fContentView->fTextView->GoToLine(0);
2068 	}
2069 
2070 	if (enclosures && enclosures->HasRef("refs"))
2071 		AddEnclosure(enclosures);
2072 
2073 	Unlock();
2074 }
2075 
2076 
2077 void
2078 TMailWindow::CopyMessage(entry_ref *ref, TMailWindow *src)
2079 {
2080 	BNode file(ref);
2081 	if (file.InitCheck() == B_OK) {
2082 		BString string;
2083 		if (fHeaderView->fTo
2084 			&& file.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK)
2085 			fHeaderView->fTo->SetText(string.String());
2086 
2087 		if (fHeaderView->fSubject
2088 			&& file.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK)
2089 			fHeaderView->fSubject->SetText(string.String());
2090 
2091 		if (fHeaderView->fCc
2092 			&& file.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK)
2093 			fHeaderView->fCc->SetText(string.String());
2094 	}
2095 
2096 	TTextView *text = src->fContentView->fTextView;
2097 	text_run_array *style = text->RunArray(0, text->TextLength());
2098 
2099 	fContentView->fTextView->SetText(text->Text(), text->TextLength(), style);
2100 
2101 	free(style);
2102 }
2103 
2104 
2105 void
2106 TMailWindow::Reply(entry_ref *ref, TMailWindow *window, uint32 type)
2107 {
2108 	fRepliedMail = *ref;
2109 	SetOriginatingWindow(window);
2110 
2111 	BEmailMessage *mail = window->Mail();
2112 	if (mail == NULL)
2113 		return;
2114 
2115 	if (type == M_REPLY_ALL)
2116 		type = B_MAIL_REPLY_TO_ALL;
2117 	else if (type == M_REPLY_TO_SENDER)
2118 		type = B_MAIL_REPLY_TO_SENDER;
2119 	else
2120 		type = B_MAIL_REPLY_TO;
2121 
2122 	uint32 useAccountFrom = fApp->UseAccountFrom();
2123 
2124 	fMail = mail->ReplyMessage(mail_reply_to_mode(type),
2125 		useAccountFrom == ACCOUNT_FROM_MAIL, QUOTE);
2126 
2127 	// set header fields
2128 	fHeaderView->fTo->SetText(fMail->To());
2129 	fHeaderView->fCc->SetText(fMail->CC());
2130 	fHeaderView->fSubject->SetText(fMail->Subject());
2131 
2132 	int32 accountID;
2133 	BFile file(window->fRef, B_READ_ONLY);
2134 	if (file.ReadAttr("MAIL:reply_with", B_INT32_TYPE, 0, &accountID,
2135 		sizeof(int32)) != B_OK)
2136 		accountID = -1;
2137 
2138 	// set mail account
2139 
2140 	if ((useAccountFrom == ACCOUNT_FROM_MAIL) || (accountID > -1)) {
2141 		if (useAccountFrom == ACCOUNT_FROM_MAIL)
2142 			fHeaderView->fAccountID = fMail->Account();
2143 		else
2144 			fHeaderView->fAccountID = accountID;
2145 
2146 		BMenu *menu = fHeaderView->fAccountMenu;
2147 		for (int32 i = menu->CountItems(); i-- > 0;) {
2148 			BMenuItem *item = menu->ItemAt(i);
2149 			BMessage *msg;
2150 			if (item && (msg = item->Message()) != NULL
2151 				&& msg->FindInt32("id") == fHeaderView->fAccountID)
2152 				item->SetMarked(true);
2153 		}
2154 	}
2155 
2156 	// create preamble string
2157 
2158 	BString preamble = fApp->ReplyPreamble();
2159 
2160 	BString name;
2161 	mail->GetName(&name);
2162 	if (name.Length() <= 0)
2163 		name = B_TRANSLATE("(Name unavailable)");
2164 
2165 	BString address(mail->From());
2166 	if (address.Length() <= 0)
2167 		address = B_TRANSLATE("(Address unavailable)");
2168 
2169 	BString date(mail->Date());
2170 	if (date.Length() <= 0)
2171 		date = B_TRANSLATE("(Date unavailable)");
2172 
2173 	preamble.ReplaceAll("%n", name);
2174 	preamble.ReplaceAll("%e", address);
2175 	preamble.ReplaceAll("%d", date);
2176 	preamble.ReplaceAll("\\n", "\n");
2177 
2178 	// insert (if selection) or load (if whole mail) message text into text view
2179 
2180 	int32 finish, start;
2181 	window->fContentView->fTextView->GetSelection(&start, &finish);
2182 	if (start != finish) {
2183 		char *text = (char *)malloc(finish - start + 1);
2184 		if (text == NULL)
2185 			return;
2186 
2187 		window->fContentView->fTextView->GetText(start, finish - start, text);
2188 		if (text[strlen(text) - 1] != '\n') {
2189 			text[strlen(text)] = '\n';
2190 			finish++;
2191 		}
2192 		fContentView->fTextView->SetText(text, finish - start);
2193 		free(text);
2194 
2195 		finish = fContentView->fTextView->CountLines();
2196 		for (int32 loop = 0; loop < finish; loop++) {
2197 			fContentView->fTextView->GoToLine(loop);
2198 			fContentView->fTextView->Insert((const char *)QUOTE);
2199 		}
2200 
2201 		if (fApp->ColoredQuotes()) {
2202 			const BFont *font = fContentView->fTextView->Font();
2203 			int32 length = fContentView->fTextView->TextLength();
2204 
2205 			TextRunArray style(length / 8 + 8);
2206 
2207 			FillInQuoteTextRuns(fContentView->fTextView, NULL,
2208 				fContentView->fTextView->Text(), length, font, &style.Array(),
2209 				style.MaxEntries());
2210 
2211 			fContentView->fTextView->SetRunArray(0, length, &style.Array());
2212 		}
2213 
2214 		fContentView->fTextView->GoToLine(0);
2215 		if (preamble.Length() > 0)
2216 			fContentView->fTextView->Insert(preamble);
2217 	} else {
2218 		fContentView->fTextView->LoadMessage(mail, true, preamble);
2219 	}
2220 
2221 	fReplying = true;
2222 }
2223 
2224 
2225 status_t
2226 TMailWindow::Send(bool now)
2227 {
2228 	uint32 characterSetToUse = fApp->MailCharacterSet();
2229 	mail_encoding encodingForBody = quoted_printable;
2230 	mail_encoding encodingForHeaders = quoted_printable;
2231 
2232 	if (!now) {
2233 		status_t status = SaveAsDraft();
2234 		if (status != B_OK) {
2235 			beep();
2236 			BAlert* alert = new BAlert("", B_TRANSLATE("E-mail draft could "
2237 				"not be saved!"), B_TRANSLATE("OK"));
2238 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2239 			alert->Go();
2240 		}
2241 		return status;
2242 	}
2243 
2244 	if (fHeaderView != NULL)
2245 		characterSetToUse = fHeaderView->fCharacterSetUserSees;
2246 
2247 	// Set up the encoding to use for converting binary to printable ASCII.
2248 	// Normally this will be quoted printable, but for some old software,
2249 	// particularly Japanese stuff, they only understand base64.  They also
2250 	// prefer it for the smaller size.  Later on this will be reduced to 7bit
2251 	// if the encoded text is just 7bit characters.
2252 	if (characterSetToUse == B_SJIS_CONVERSION
2253 		|| characterSetToUse == B_EUC_CONVERSION)
2254 		encodingForBody = base64;
2255 	else if (characterSetToUse == B_JIS_CONVERSION
2256 		|| characterSetToUse == B_MAIL_US_ASCII_CONVERSION
2257 		|| characterSetToUse == B_ISO1_CONVERSION
2258 		|| characterSetToUse == B_EUC_KR_CONVERSION)
2259 		encodingForBody = eight_bit;
2260 
2261 	// Using quoted printable headers on almost completely non-ASCII Japanese
2262 	// is a waste of time.  Besides, some stupid cell phone services need
2263 	// base64 in the headers.
2264 	if (characterSetToUse == B_SJIS_CONVERSION
2265 		|| characterSetToUse == B_EUC_CONVERSION
2266 		|| characterSetToUse == B_JIS_CONVERSION
2267 		|| characterSetToUse == B_EUC_KR_CONVERSION)
2268 		encodingForHeaders = base64;
2269 
2270 	// Count the number of characters in the message body which aren't in the
2271 	// currently selected character set.  Also see if the resulting encoded
2272 	// text can safely use 7 bit characters.
2273 	if (fContentView->fTextView->TextLength() > 0) {
2274 		// First do a trial encoding with the user's character set.
2275 		int32 converterState = 0;
2276 		int32 originalLength;
2277 		BString tempString;
2278 		int32 tempStringLength;
2279 		char* tempStringPntr;
2280 		originalLength = fContentView->fTextView->TextLength();
2281 		tempStringLength = originalLength * 6;
2282 			// Some character sets bloat up on escape codes
2283 		tempStringPntr = tempString.LockBuffer (tempStringLength);
2284 		if (tempStringPntr != NULL && mail_convert_from_utf8(characterSetToUse,
2285 				fContentView->fTextView->Text(), &originalLength,
2286 				tempStringPntr, &tempStringLength, &converterState,
2287 				0x1A /* used for unknown characters */) == B_OK) {
2288 			// Check for any characters which don't fit in a 7 bit encoding.
2289 			int i;
2290 			bool has8Bit = false;
2291 			for (i = 0; i < tempStringLength; i++) {
2292 				if (tempString[i] == 0 || (tempString[i] & 0x80)) {
2293 					has8Bit = true;
2294 					break;
2295 				}
2296 			}
2297 			if (!has8Bit)
2298 				encodingForBody = seven_bit;
2299 			tempString.UnlockBuffer (tempStringLength);
2300 
2301 			// Count up the number of unencoded characters and warn the user
2302 			if (fApp->WarnAboutUnencodableCharacters()) {
2303 				// TODO: ideally, the encoding should be silently changed to
2304 				// one that can express this character
2305 				int32 offset = 0;
2306 				int count = 0;
2307 				while (offset >= 0) {
2308 					offset = tempString.FindFirst (0x1A, offset);
2309 					if (offset >= 0) {
2310 						count++;
2311 						offset++;
2312 							// Don't get stuck finding the same character again.
2313 					}
2314 				}
2315 				if (count > 0) {
2316 					int32 userAnswer;
2317 					BString	messageString;
2318 					BString countString;
2319 					countString << count;
2320 					messageString << B_TRANSLATE("Your main text contains %ld"
2321 						" unencodable characters. Perhaps a different "
2322 						"character set would work better? Hit Send to send it "
2323 						"anyway "
2324 						"(a substitute character will be used in place of "
2325 						"the unencodable ones), or choose Cancel to go back "
2326 						"and try fixing it up.");
2327 					messageString.ReplaceFirst("%ld", countString);
2328 					BAlert* alert = new BAlert("Question", messageString.String(),
2329 						B_TRANSLATE("Send"),
2330 						B_TRANSLATE("Cancel"),
2331 						NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
2332 						B_WARNING_ALERT);
2333 					alert->SetShortcut(1, B_ESCAPE);
2334 					userAnswer = alert->Go();
2335 
2336 					if (userAnswer == 1) {
2337 						// Cancel was picked.
2338 						return -1;
2339 					}
2340 				}
2341 			}
2342 		}
2343 	}
2344 
2345 	Hide();
2346 		// depending on the system (and I/O) load, this could take a while
2347 		// but the user shouldn't be left waiting
2348 
2349 	status_t result;
2350 
2351 	if (fResending) {
2352 		BFile file(fRef, O_RDONLY);
2353 		result = file.InitCheck();
2354 		if (result == B_OK) {
2355 			BEmailMessage mail(&file);
2356 			mail.SetTo(fHeaderView->fTo->Text(), characterSetToUse,
2357 				encodingForHeaders);
2358 
2359 			if (fHeaderView->fAccountID != ~0L)
2360 				mail.SendViaAccount(fHeaderView->fAccountID);
2361 
2362 			result = mail.Send(now);
2363 		}
2364 	} else {
2365 		if (fMail == NULL)
2366 			// the mail will be deleted when the window is closed
2367 			fMail = new BEmailMessage;
2368 
2369 		// Had an embarrassing bug where replying to a message and clearing the
2370 		// CC field meant that it got sent out anyway, so pass in empty strings
2371 		// when changing the header to force it to remove the header.
2372 
2373 		fMail->SetTo(fHeaderView->fTo->Text(), characterSetToUse,
2374 			encodingForHeaders);
2375 		fMail->SetSubject(fHeaderView->fSubject->Text(), characterSetToUse,
2376 			encodingForHeaders);
2377 		fMail->SetCC(fHeaderView->fCc->Text(), characterSetToUse,
2378 			encodingForHeaders);
2379 		fMail->SetBCC(fHeaderView->fBcc->Text());
2380 
2381 		//--- Add X-Mailer field
2382 		{
2383 			// get app version
2384 			version_info info;
2385 			memset(&info, 0, sizeof(version_info));
2386 
2387 			app_info appInfo;
2388 			if (be_app->GetAppInfo(&appInfo) == B_OK) {
2389 				BFile file(&appInfo.ref, B_READ_ONLY);
2390 				if (file.InitCheck() == B_OK) {
2391 					BAppFileInfo appFileInfo(&file);
2392 					if (appFileInfo.InitCheck() == B_OK)
2393 						appFileInfo.GetVersionInfo(&info, B_APP_VERSION_KIND);
2394 				}
2395 			}
2396 
2397 			char versionString[255];
2398 			sprintf(versionString,
2399 				"Mail/Haiku %" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32,
2400 				info.major, info.middle, info.minor);
2401 			fMail->SetHeaderField("X-Mailer", versionString);
2402 		}
2403 
2404 		/****/
2405 
2406 		// the content text is always added to make sure there is a mail body
2407 		fMail->SetBodyTextTo("");
2408 		fContentView->fTextView->AddAsContent(fMail, fApp->WrapMode(),
2409 			characterSetToUse, encodingForBody);
2410 
2411 		if (fEnclosuresView != NULL) {
2412 			TListItem *item;
2413 			int32 index = 0;
2414 			while ((item = (TListItem *)fEnclosuresView->fList->ItemAt(index++))
2415 				!= NULL) {
2416 				if (item->Component())
2417 					continue;
2418 
2419 				// leave out missing enclosures
2420 				BEntry entry(item->Ref());
2421 				if (!entry.Exists())
2422 					continue;
2423 
2424 				fMail->Attach(item->Ref(), fApp->AttachAttributes());
2425 			}
2426 		}
2427 		if (fHeaderView->fAccountID != ~0L)
2428 			fMail->SendViaAccount(fHeaderView->fAccountID);
2429 
2430 		result = fMail->Send(now);
2431 
2432 		if (fReplying) {
2433 			// Set status of the replied mail
2434 
2435 			BNode node(&fRepliedMail);
2436 			if (node.InitCheck() >= B_OK) {
2437 				if (fOriginatingWindow) {
2438 					BMessage msg(M_SAVE_POSITION), reply;
2439 					fOriginatingWindow->SendMessage(&msg, &reply);
2440 				}
2441 				WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Replied");
2442 			}
2443 		}
2444 	}
2445 
2446 	bool close = false;
2447 	BString errorMessage;
2448 
2449 	switch (result) {
2450 		case B_OK:
2451 			close = true;
2452 			fSent = true;
2453 
2454 			// If it's a draft, remove the draft file
2455 			if (fDraft) {
2456 				BEntry entry(fRef);
2457 				entry.Remove();
2458 			}
2459 			break;
2460 
2461 		case B_MAIL_NO_DAEMON:
2462 		{
2463 			close = true;
2464 			fSent = true;
2465 
2466 			BAlert* alert = new BAlert("no daemon",
2467 				B_TRANSLATE("The mail_daemon is not running. The message is "
2468 				"queued and will be sent when the mail_daemon is started."),
2469 				B_TRANSLATE("Start now"), B_TRANSLATE("OK"));
2470 			alert->SetShortcut(1, B_ESCAPE);
2471 			int32 start = alert->Go();
2472 
2473 			if (start == 0) {
2474 				result = be_roster->Launch("application/x-vnd.Be-POST");
2475 				if (result == B_OK) {
2476 					BMailDaemon::SendQueuedMail();
2477 				} else {
2478 					errorMessage
2479 						<< B_TRANSLATE("The mail_daemon could not be "
2480 							"started:\n\t")
2481 						<< strerror(result);
2482 				}
2483 			}
2484 			break;
2485 		}
2486 
2487 //		case B_MAIL_UNKNOWN_HOST:
2488 //		case B_MAIL_ACCESS_ERROR:
2489 //			sprintf(errorMessage,
2490 //				"An error occurred trying to connect with the SMTP "
2491 //				"host.  Check your SMTP host name.");
2492 //			break;
2493 //
2494 //		case B_MAIL_NO_RECIPIENT:
2495 //			sprintf(errorMessage,
2496 //				"You must have either a \"To\" or \"Bcc\" recipient.");
2497 //			break;
2498 
2499 		default:
2500 			errorMessage << "An error occurred trying to send mail:\n\t"
2501 				<< strerror(result);
2502 			break;
2503 	}
2504 
2505 	if (result != B_NO_ERROR && result != B_MAIL_NO_DAEMON) {
2506 		beep();
2507 		BAlert* alert = new BAlert("", errorMessage.String(), B_TRANSLATE("OK"));
2508 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2509 		alert->Go();
2510 	}
2511 	if (close) {
2512 		PostMessage(B_QUIT_REQUESTED);
2513 	} else {
2514 		// The window was hidden earlier
2515 		Show();
2516 	}
2517 
2518 	return result;
2519 }
2520 
2521 
2522 status_t
2523 TMailWindow::SaveAsDraft()
2524 {
2525 	status_t	status;
2526 	BPath		draftPath;
2527 	BDirectory	dir;
2528 	BFile		draft;
2529 	uint32		flags = 0;
2530 
2531 	if (fDraft) {
2532 		if ((status = draft.SetTo(fRef,
2533 			B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE)) != B_OK) {
2534 			return status;
2535 		}
2536 	} else {
2537 		// Get the user home directory
2538 		if ((status = find_directory(B_USER_DIRECTORY, &draftPath)) != B_OK)
2539 			return status;
2540 
2541 		// Append the relative path of the draft directory
2542 		draftPath.Append(kDraftPath);
2543 
2544 		// Create the file
2545 		status = dir.SetTo(draftPath.Path());
2546 		switch (status) {
2547 			// Create the directory if it does not exist
2548 			case B_ENTRY_NOT_FOUND:
2549 				if ((status = dir.CreateDirectory(draftPath.Path(), &dir))
2550 					!= B_OK)
2551 					return status;
2552 			case B_OK:
2553 			{
2554 				char fileName[B_FILE_NAME_LENGTH];
2555 				// save as some version of the message's subject
2556 				if (strlen(fHeaderView->fSubject->Text()) == 0)
2557 					strlcpy(fileName, B_TRANSLATE("Untitled"),
2558 						sizeof(fileName));
2559 				else
2560 					strlcpy(fileName, fHeaderView->fSubject->Text(),
2561 						sizeof(fileName));
2562 
2563 				uint32 originalLength = strlen(fileName);
2564 
2565 				// convert /, \ and : to -
2566 				for (char *bad = fileName; (bad = strchr(bad, '/')) != NULL;
2567 					++bad) *bad = '-';
2568 				for (char *bad = fileName; (bad = strchr(bad, '\\')) != NULL;
2569 					++bad) *bad = '-';
2570 				for (char *bad = fileName; (bad = strchr(bad, ':')) != NULL;
2571 					++bad) *bad = '-';
2572 
2573 				// Create the file; if the name exists, find a unique name
2574 				flags = B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS;
2575 				int32 i = 1;
2576 				do {
2577 					status = draft.SetTo(&dir, fileName, flags);
2578 					if (status == B_OK)
2579 						break;
2580 					char appendix[B_FILE_NAME_LENGTH];
2581 					sprintf(appendix, " %" B_PRId32, i++);
2582 					int32 pos = min_c(sizeof(fileName) - strlen(appendix),
2583 						originalLength);
2584 					sprintf(fileName + pos, "%s", appendix);
2585 				} while (status == B_FILE_EXISTS);
2586 				if (status != B_OK)
2587 					return status;
2588 
2589 				// Cache the ref
2590 				if (fRef == NULL)
2591 					fRef = new entry_ref;
2592 				BEntry entry(&dir, fileName);
2593 				entry.GetRef(fRef);
2594 				break;
2595 			}
2596 			default:
2597 				return status;
2598 		}
2599 	}
2600 
2601 	// Write the content of the message
2602 	draft.Write(fContentView->fTextView->Text(),
2603 		fContentView->fTextView->TextLength());
2604 
2605 	//
2606 	// Add the header stuff as attributes
2607 	//
2608 	WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->fTo->Text());
2609 	WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->fTo->Text());
2610 	WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->fSubject->Text());
2611 	if (fHeaderView->fCc != NULL)
2612 		WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->fCc->Text());
2613 	if (fHeaderView->fBcc != NULL)
2614 		WriteAttrString(&draft, B_MAIL_ATTR_BCC, fHeaderView->fBcc->Text());
2615 
2616 	// Add account
2617 	BMenuItem* menuItem = fHeaderView->fAccountMenu->FindMarked();
2618 	if (menuItem != NULL)
2619 		WriteAttrString(&draft, B_MAIL_ATTR_ACCOUNT, menuItem->Label());
2620 
2621 	// Add encoding
2622 	menuItem = fHeaderView->fEncodingMenu->FindMarked();
2623 	if (menuItem != NULL)
2624 		WriteAttrString(&draft, "MAIL:encoding", menuItem->Label());
2625 
2626 	// Add the draft attribute for indexing
2627 	uint32 draftAttr = true;
2628 	draft.WriteAttr("MAIL:draft", B_INT32_TYPE, 0, &draftAttr, sizeof(uint32));
2629 
2630 	// Add Attachment paths in attribute
2631 	if (fEnclosuresView != NULL) {
2632 		TListItem *item;
2633 		BPath path;
2634 		BString pathStr;
2635 
2636 		for (int32 i = 0; (item = (TListItem *)
2637 			fEnclosuresView->fList->ItemAt(i)) != NULL; i++) {
2638 			if (i > 0)
2639 				pathStr.Append(":");
2640 
2641 			BEntry entry(item->Ref(), true);
2642 			if (!entry.Exists())
2643 				continue;
2644 
2645 			entry.GetPath(&path);
2646 			pathStr.Append(path.Path());
2647 		}
2648 		if (pathStr.Length())
2649 			draft.WriteAttrString("MAIL:attachments", &pathStr);
2650 	}
2651 
2652 	// Set the MIME Type of the file
2653 	BNodeInfo info(&draft);
2654 	info.SetType(kDraftType);
2655 
2656 	fDraft = true;
2657 	fChanged = false;
2658 
2659 	fSaveButton->SetEnabled(false);
2660 
2661 	return B_OK;
2662 }
2663 
2664 
2665 status_t
2666 TMailWindow::TrainMessageAs(const char *CommandWord)
2667 {
2668 	status_t	errorCode = -1;
2669 	char		errorString[1500];
2670 	BEntry		fileEntry;
2671 	BPath		filePath;
2672 	BMessage	replyMessage;
2673 	BMessage	scriptingMessage;
2674 	team_id		serverTeam;
2675 
2676 	if (fRef == NULL)
2677 		goto ErrorExit; // Need to have a real file and name.
2678 	errorCode = fileEntry.SetTo(fRef, true /* traverse */);
2679 	if (errorCode != B_OK)
2680 		goto ErrorExit;
2681 	errorCode = fileEntry.GetPath(&filePath);
2682 	if (errorCode != B_OK)
2683 		goto ErrorExit;
2684 	fileEntry.Unset();
2685 
2686 	// Get a connection to the spam database server.  Launch if needed.
2687 
2688 	if (!fMessengerToSpamServer.IsValid()) {
2689 		// Make sure the server is running.
2690 		if (!be_roster->IsRunning (kSpamServerSignature)) {
2691 			errorCode = be_roster->Launch (kSpamServerSignature);
2692 			if (errorCode != B_OK) {
2693 				BPath path;
2694 				entry_ref ref;
2695 				directory_which places[]
2696 					= {B_SYSTEM_NONPACKAGED_BIN_DIRECTORY, B_SYSTEM_BIN_DIRECTORY};
2697 				for (int32 i = 0; i < 2; i++) {
2698 					find_directory(places[i],&path);
2699 					path.Append("spamdbm");
2700 					if (!BEntry(path.Path()).Exists())
2701 						continue;
2702 					get_ref_for_path(path.Path(),&ref);
2703 					if ((errorCode =  be_roster->Launch (&ref)) == B_OK)
2704 						break;
2705 				}
2706 				if (errorCode != B_OK)
2707 					goto ErrorExit;
2708 			}
2709 		}
2710 
2711 		// Set up the messenger to the database server.
2712 		errorCode = B_SERVER_NOT_FOUND;
2713 		serverTeam = be_roster->TeamFor(kSpamServerSignature);
2714 		if (serverTeam < 0)
2715 			goto ErrorExit;
2716 
2717 		fMessengerToSpamServer = BMessenger (kSpamServerSignature, serverTeam,
2718 			&errorCode);
2719 
2720 		if (!fMessengerToSpamServer.IsValid())
2721 			goto ErrorExit;
2722 	}
2723 
2724 	// Ask the server to train on the message.  Give it the command word and
2725 	// the absolute path name to use.
2726 
2727 	scriptingMessage.MakeEmpty();
2728 	scriptingMessage.what = B_SET_PROPERTY;
2729 	scriptingMessage.AddSpecifier(CommandWord);
2730 	errorCode = scriptingMessage.AddData("data", B_STRING_TYPE,
2731 		filePath.Path(), strlen(filePath.Path()) + 1, false /* fixed size */);
2732 	if (errorCode != B_OK)
2733 		goto ErrorExit;
2734 	replyMessage.MakeEmpty();
2735 	errorCode = fMessengerToSpamServer.SendMessage(&scriptingMessage,
2736 		&replyMessage);
2737 	if (errorCode != B_OK
2738 		|| replyMessage.FindInt32("error", &errorCode) != B_OK
2739 		|| errorCode != B_OK)
2740 		goto ErrorExit; // Classification failed in one of many ways.
2741 
2742 	SetTitleForMessage();
2743 		// Update window title to show new spam classification.
2744 	return B_OK;
2745 
2746 ErrorExit:
2747 	beep();
2748 	sprintf(errorString, "Unable to train the message file \"%s\" as %s.  "
2749 		"Possibly useful error code: %s (%" B_PRId32 ").",
2750 		filePath.Path(), CommandWord, strerror(errorCode), errorCode);
2751 	BAlert* alert = new BAlert("", errorString,	B_TRANSLATE("OK"));
2752 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2753 	alert->Go();
2754 
2755 	return errorCode;
2756 }
2757 
2758 
2759 void
2760 TMailWindow::SetTitleForMessage()
2761 {
2762 	//
2763 	//	Figure out the title of this message and set the title bar
2764 	//
2765 	BString title = B_TRANSLATE_SYSTEM_NAME("Mail");
2766 
2767 	if (fIncoming) {
2768 		if (fMail->GetName(&title) == B_OK)
2769 			title << ": \"" << fMail->Subject() << "\"";
2770 		else
2771 			title = fMail->Subject();
2772 
2773 		if (fDownloading)
2774 			title.Prepend("Downloading: ");
2775 
2776 		if (fApp->ShowSpamGUI() && fRef != NULL) {
2777 			BString	classification;
2778 			BNode	node (fRef);
2779 			char	numberString [30];
2780 			BString oldTitle (title);
2781 			float	spamRatio;
2782 			if (node.InitCheck() != B_OK || node.ReadAttrString
2783 				("MAIL:classification", &classification) != B_OK)
2784 				classification = "Unrated";
2785 			if (classification != "Spam" && classification != "Genuine") {
2786 				// Uncertain, Unrated and other unknown classes, show the ratio.
2787 				if (node.InitCheck() == B_OK && sizeof (spamRatio) ==
2788 					node.ReadAttr("MAIL:ratio_spam", B_FLOAT_TYPE, 0,
2789 					&spamRatio, sizeof (spamRatio))) {
2790 					sprintf (numberString, "%.4f", spamRatio);
2791 					classification << " " << numberString;
2792 				}
2793 			}
2794 			title = "";
2795 			title << "[" << classification << "] " << oldTitle;
2796 		}
2797 	}
2798 	SetTitle(title);
2799 }
2800 
2801 
2802 //
2803 //	Open *another* message in the existing mail window.  Some code here is
2804 //	duplicated from various constructors.
2805 //	The duplicated code should be in a private initializer method -- axeld.
2806 //
2807 
2808 status_t
2809 TMailWindow::OpenMessage(const entry_ref *ref, uint32 characterSetForDecoding)
2810 {
2811 	if (ref == NULL)
2812 		return B_ERROR;
2813 	//
2814 	//	Set some references to the email file
2815 	//
2816 	delete fRef;
2817 	fRef = new entry_ref(*ref);
2818 
2819 	fPrevTrackerPositionSaved = false;
2820 	fNextTrackerPositionSaved = false;
2821 
2822 	fContentView->fTextView->StopLoad();
2823 	delete fMail;
2824 	fMail = NULL;
2825 
2826 	BFile file(fRef, B_READ_ONLY);
2827 	status_t err = file.InitCheck();
2828 	if (err != B_OK)
2829 		return err;
2830 
2831 	char mimeType[256];
2832 	BNodeInfo fileInfo(&file);
2833 	fileInfo.GetType(mimeType);
2834 
2835 	if (strcmp(mimeType, B_PARTIAL_MAIL_TYPE) == 0) {
2836 		BMessenger listener(this);
2837 		BMailDaemon::FetchBody(*ref, &listener);
2838 		fileInfo.GetType(mimeType);
2839 		_SetDownloading(true);
2840 	} else
2841 		_SetDownloading(false);
2842 
2843 	// Check if it's a draft file, which contains only the text, and has the
2844 	// from, to, bcc, attachments listed as attributes.
2845 	if (strcmp(kDraftType, mimeType) == 0) {
2846 		BNode node(fRef);
2847 		off_t size;
2848 		BString string;
2849 
2850 		fMail = new BEmailMessage; // Not really used much, but still needed.
2851 
2852 		// Load the raw UTF-8 text from the file.
2853 		file.GetSize(&size);
2854 		fContentView->fTextView->SetText(&file, 0, size);
2855 
2856 		// Restore Fields from attributes
2857 		if (node.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK)
2858 			fHeaderView->fTo->SetText(string.String());
2859 		if (node.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK)
2860 			fHeaderView->fSubject->SetText(string.String());
2861 		if (node.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK)
2862 			fHeaderView->fCc->SetText(string.String());
2863 		if (node.ReadAttrString(B_MAIL_ATTR_BCC, &string) == B_OK)
2864 			fHeaderView->fBcc->SetText(string.String());
2865 
2866 		// Restore account
2867 		if (node.ReadAttrString(B_MAIL_ATTR_ACCOUNT, &string) == B_OK) {
2868 			BMenuItem* accountItem = fHeaderView->fAccountMenu->FindItem(string.String());
2869 			if (accountItem != NULL)
2870 				accountItem->SetMarked(true);
2871 		}
2872 
2873 		// Restore encoding
2874 		if (node.ReadAttrString("MAIL:encoding", &string) == B_OK) {
2875 			BMenuItem* encodingItem = fHeaderView->fEncodingMenu->FindItem(string.String());
2876 			if (encodingItem != NULL)
2877 				encodingItem->SetMarked(true);
2878 		}
2879 
2880 		// Restore attachments
2881 		if (node.ReadAttrString("MAIL:attachments", &string) == B_OK) {
2882 			BMessage msg(REFS_RECEIVED);
2883 			entry_ref enc_ref;
2884 
2885 			char *s = strtok((char *)string.String(), ":");
2886 			while (s) {
2887 				BEntry entry(s, true);
2888 				if (entry.Exists()) {
2889 					entry.GetRef(&enc_ref);
2890 					msg.AddRef("refs", &enc_ref);
2891 				}
2892 				s = strtok(NULL, ":");
2893 			}
2894 			AddEnclosure(&msg);
2895 		}
2896 
2897 		// restore the reading position if available
2898 		PostMessage(M_READ_POS);
2899 
2900 		PostMessage(RESET_BUTTONS);
2901 		fIncoming = false;
2902 		fDraft = true;
2903 	} else {
2904 		// A real mail message, parse its headers to get from, to, etc.
2905 		fMail = new BEmailMessage(fRef, characterSetForDecoding);
2906 		fIncoming = true;
2907 		fHeaderView->LoadMessage(fMail);
2908 	}
2909 
2910 	err = fMail->InitCheck();
2911 	if (err < B_OK) {
2912 		delete fMail;
2913 		fMail = NULL;
2914 		return err;
2915 	}
2916 
2917 	SetTitleForMessage();
2918 
2919 	if (fIncoming) {
2920 		//
2921 		//	Put the addresses in the 'Save Address' Menu
2922 		//
2923 		BMenuItem *item;
2924 		while ((item = fSaveAddrMenu->RemoveItem((int32)0)) != NULL)
2925 			delete item;
2926 
2927 		// create the list of addresses
2928 
2929 		BList addressList;
2930 		get_address_list(addressList, fMail->To(), extract_address);
2931 		get_address_list(addressList, fMail->CC(), extract_address);
2932 		get_address_list(addressList, fMail->From(), extract_address);
2933 		get_address_list(addressList, fMail->ReplyTo(), extract_address);
2934 
2935 		BMessage *msg;
2936 
2937 		for (int32 i = addressList.CountItems(); i-- > 0;) {
2938 			char *address = (char *)addressList.RemoveItem((int32)0);
2939 
2940 			// insert the new address in alphabetical order
2941 			int32 index = 0;
2942 			while ((item = fSaveAddrMenu->ItemAt(index)) != NULL) {
2943 				if (!strcmp(address, item->Label())) {
2944 					// item already in list
2945 					goto skip;
2946 				}
2947 
2948 				if (strcmp(address, item->Label()) < 0)
2949 					break;
2950 
2951 				index++;
2952 			}
2953 
2954 			msg = new BMessage(M_SAVE);
2955 			msg->AddString("address", address);
2956 			fSaveAddrMenu->AddItem(new BMenuItem(address, msg), index);
2957 
2958 		skip:
2959 			free(address);
2960 		}
2961 
2962 		//
2963 		// Clear out existing contents of text view.
2964 		//
2965 		fContentView->fTextView->SetText("", (int32)0);
2966 
2967 		fContentView->fTextView->LoadMessage(fMail, false, NULL);
2968 
2969 		if (fApp->ShowButtonBar())
2970 			_UpdateReadButton();
2971 	}
2972 
2973 	return B_OK;
2974 }
2975 
2976 
2977 TMailWindow *
2978 TMailWindow::FrontmostWindow()
2979 {
2980 	BAutolock locker(sWindowListLock);
2981 	if (sWindowList.CountItems() > 0)
2982 		return (TMailWindow *)sWindowList.ItemAt(0);
2983 
2984 	return NULL;
2985 }
2986 
2987 
2988 /*
2989 // Copied from src/kits/tracker/FindPanel.cpp.
2990 uint32
2991 TMailWindow::InitialMode(const BNode *node)
2992 {
2993 	if (!node || node->InitCheck() != B_OK)
2994 		return kByNameItem;
2995 
2996 	uint32 result;
2997 	if (node->ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0,
2998 		(int32 *)&result, sizeof(int32)) <= 0)
2999 		return kByNameItem;
3000 
3001 	return result;
3002 }
3003 
3004 
3005 // Copied from src/kits/tracker/FindPanel.cpp.
3006 int32
3007 TMailWindow::InitialAttrCount(const BNode *node)
3008 {
3009 	if (!node || node->InitCheck() != B_OK)
3010 		return 1;
3011 
3012 	int32 result;
3013 	if (node->ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0,
3014 		&result, sizeof(int32)) <= 0)
3015 		return 1;
3016 
3017 	return result;
3018 }*/
3019 
3020 
3021 // #pragma mark -
3022 
3023 
3024 void
3025 TMailWindow::_UpdateSizeLimits()
3026 {
3027 	float minWidth, maxWidth, minHeight, maxHeight;
3028 	GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
3029 
3030 	float height;
3031 	fMenuBar->GetPreferredSize(&minWidth, &height);
3032 
3033 	minHeight = height;
3034 
3035 	if (fButtonBar) {
3036 		fButtonBar->GetPreferredSize(&minWidth, &height);
3037 		minHeight += height;
3038 	} else {
3039 		minWidth = WIND_WIDTH;
3040 	}
3041 
3042 	minHeight += fHeaderView->Bounds().Height() + ENCLOSURES_HEIGHT + 60;
3043 
3044 	SetSizeLimits(minWidth, RIGHT_BOUNDARY, minHeight, RIGHT_BOUNDARY);
3045 }
3046 
3047 
3048 status_t
3049 TMailWindow::_GetQueryPath(BPath* queryPath) const
3050 {
3051 	// get the user home directory and from there the query folder
3052 	status_t ret = find_directory(B_USER_DIRECTORY, queryPath);
3053 	if (ret == B_OK)
3054 		ret = queryPath->Append(kQueriesDirectory);
3055 
3056 	return ret;
3057 }
3058 
3059 
3060 void
3061 TMailWindow::_RebuildQueryMenu(bool firstTime)
3062 {
3063 	while (fQueryMenu->ItemAt(0)) {
3064 		BMenuItem* item = fQueryMenu->RemoveItem((int32)0);
3065 		delete item;
3066 	}
3067 
3068 	fQueryMenu->AddItem(new BMenuItem(B_TRANSLATE("Edit queries"
3069 			B_UTF8_ELLIPSIS),
3070 		new BMessage(M_EDIT_QUERIES), 'E', B_SHIFT_KEY));
3071 
3072 	bool queryItemsAdded = false;
3073 
3074 	BPath queryPath;
3075 	if (_GetQueryPath(&queryPath) < B_OK)
3076 		return;
3077 
3078 	BDirectory queryDir(queryPath.Path());
3079 
3080 	if (firstTime) {
3081 		BPrivate::BPathMonitor::StartWatching(queryPath.Path(),
3082 			B_WATCH_RECURSIVELY, BMessenger(this, this));
3083 	}
3084 
3085 	// If we find the named query, add it to the menu.
3086 	BEntry entry;
3087 	while (queryDir.GetNextEntry(&entry) == B_OK) {
3088 		char name[B_FILE_NAME_LENGTH + 1];
3089 		entry.GetName(name);
3090 
3091 		char* queryString = _BuildQueryString(&entry);
3092 		if (queryString == NULL)
3093 			continue;
3094 
3095 		queryItemsAdded = true;
3096 
3097 		QueryMenu* queryMenu = new QueryMenu(name, false);
3098 		queryMenu->SetTargetForItems(be_app);
3099 		queryMenu->SetPredicate(queryString);
3100 		fQueryMenu->AddItem(queryMenu);
3101 
3102 		free(queryString);
3103 	}
3104 
3105 	if (queryItemsAdded)
3106 		fQueryMenu->AddItem(new BSeparatorItem(), 1);
3107 }
3108 
3109 
3110 char*
3111 TMailWindow::_BuildQueryString(BEntry* entry) const
3112 {
3113 	BNode node(entry);
3114 	if (node.InitCheck() != B_OK)
3115 		return NULL;
3116 
3117 	uint32 mode;
3118 	if (node.ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, (int32*)&mode,
3119 		sizeof(int32)) <= 0) {
3120 		mode = kByNameItem;
3121 	}
3122 
3123 	BString queryString;
3124 	switch (mode) {
3125 		case kByForumlaItem:
3126 		{
3127 			BString buffer;
3128 			if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK)
3129 				queryString << buffer;
3130 			break;
3131 		}
3132 
3133 		case kByNameItem:
3134 		{
3135 			BString buffer;
3136 			if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK)
3137 				queryString << "(name==*" << buffer << "*)";
3138 			break;
3139 		}
3140 
3141 		case kByAttributeItem:
3142 		{
3143 			int32 count = 1;
3144 			if (node.ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0,
3145 					(int32 *)&count, sizeof(int32)) <= 0) {
3146 				count = 1;
3147 			}
3148 
3149 			attr_info info;
3150 			if (node.GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK)
3151 				break;
3152 
3153 			if (count > 1)
3154 				queryString << "(";
3155 
3156 			char *buffer = new char[info.size];
3157 			if (node.ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0,
3158 					buffer, (size_t)info.size) == info.size) {
3159 				BMessage message;
3160 				if (message.Unflatten(buffer) == B_OK) {
3161 					for (int32 index = 0; /*index < count*/; index++) {
3162 						const char *field;
3163 						const char *value;
3164 						if (message.FindString("menuSelection", index, &field)
3165 								!= B_OK
3166 							|| message.FindString("attrViewText", index, &value)
3167 								!= B_OK) {
3168 							break;
3169 						}
3170 
3171 						// ignore the mime type, we'll force it to be email
3172 						// later
3173 						if (strcmp(field, "BEOS:TYPE") != 0) {
3174 							// TODO: check if subMenu contains the type of
3175 							// comparison we are suppose to make here
3176 							queryString << "(" << field << "==\""
3177 								<< value << "\")";
3178 
3179 							int32 logicMenuSelectedIndex;
3180 							if (message.FindInt32("logicalRelation", index,
3181 								&logicMenuSelectedIndex) == B_OK) {
3182 								if (logicMenuSelectedIndex == 0)
3183 									queryString << "&&";
3184 								else if (logicMenuSelectedIndex == 1)
3185 									queryString << "||";
3186 							} else
3187 								break;
3188 						}
3189 					}
3190 				}
3191 			}
3192 
3193 			if (count > 1)
3194 				queryString << ")";
3195 
3196 			delete [] buffer;
3197 			break;
3198 		}
3199 
3200 		default:
3201 			break;
3202 	}
3203 
3204 	if (queryString.Length() == 0)
3205 		return NULL;
3206 
3207 	// force it to check for email only
3208 	if (queryString.FindFirst("text/x-email") < 0) {
3209 		BString temp;
3210 		temp << "(" << queryString << "&&(BEOS:TYPE==\"text/x-email\"))";
3211 		queryString = temp;
3212 	}
3213 
3214 	return strdup(queryString.String());
3215 }
3216 
3217 
3218 void
3219 TMailWindow::_AddReadButton()
3220 {
3221 	BNode node(fRef);
3222 
3223 	read_flags flag = B_UNREAD;
3224 	read_read_attr(node, flag);
3225 
3226 	int32 buttonIndex = fButtonBar->IndexOf(fNextButton);
3227 	if (flag == B_READ) {
3228 		fReadButton = fButtonBar->AddButton(B_TRANSLATE("Unread"), 28,
3229 			new BMessage(M_UNREAD), buttonIndex);
3230 	} else {
3231 		fReadButton = fButtonBar->AddButton(B_TRANSLATE(" Read "), 24,
3232 			new BMessage(M_READ), buttonIndex);
3233 	}
3234 }
3235 
3236 
3237 void
3238 TMailWindow::_UpdateReadButton()
3239 {
3240 	if (fApp->ShowButtonBar()) {
3241 		fButtonBar->RemoveButton(fReadButton);
3242 		fReadButton = NULL;
3243 		if (!fAutoMarkRead && fIncoming)
3244 			_AddReadButton();
3245 	}
3246 	UpdateViews();
3247 }
3248 
3249 
3250 void
3251 TMailWindow::_SetDownloading(bool downloading)
3252 {
3253 	fDownloading = downloading;
3254 }
3255