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