xref: /haiku/src/apps/mail/MailWindow.cpp (revision 9bd024edbe5d06358e4285100a3240e4d138a712)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2001, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
23 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or
30 registered trademarks of Be Incorporated in the United States and other
31 countries. Other brand product names are registered trademarks or trademarks
32 of their respective holders. All rights reserved.
33 */
34 
35 
36 #include "MailWindow.h"
37 
38 #include <fcntl.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <strings.h>
42 #include <sys/stat.h>
43 #include <sys/utsname.h>
44 #include <unistd.h>
45 
46 #include <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 B_MAIL_BODY_FETCHED:
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 				BMailDaemon daemon;
2478 				result = daemon.Launch();
2479 				if (result == B_OK) {
2480 					daemon.SendQueuedMail();
2481 				} else {
2482 					errorMessage
2483 						<< B_TRANSLATE("The mail_daemon could not be "
2484 							"started:\n\t")
2485 						<< strerror(result);
2486 				}
2487 			}
2488 			break;
2489 		}
2490 
2491 //		case B_MAIL_UNKNOWN_HOST:
2492 //		case B_MAIL_ACCESS_ERROR:
2493 //			sprintf(errorMessage,
2494 //				"An error occurred trying to connect with the SMTP "
2495 //				"host.  Check your SMTP host name.");
2496 //			break;
2497 //
2498 //		case B_MAIL_NO_RECIPIENT:
2499 //			sprintf(errorMessage,
2500 //				"You must have either a \"To\" or \"Bcc\" recipient.");
2501 //			break;
2502 
2503 		default:
2504 			errorMessage << "An error occurred trying to send mail:\n\t"
2505 				<< strerror(result);
2506 			break;
2507 	}
2508 
2509 	if (result != B_NO_ERROR && result != B_MAIL_NO_DAEMON) {
2510 		beep();
2511 		BAlert* alert = new BAlert("", errorMessage.String(), B_TRANSLATE("OK"));
2512 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2513 		alert->Go();
2514 	}
2515 	if (close) {
2516 		PostMessage(B_QUIT_REQUESTED);
2517 	} else {
2518 		// The window was hidden earlier
2519 		Show();
2520 	}
2521 
2522 	return result;
2523 }
2524 
2525 
2526 status_t
2527 TMailWindow::SaveAsDraft()
2528 {
2529 	status_t	status;
2530 	BPath		draftPath;
2531 	BDirectory	dir;
2532 	BFile		draft;
2533 	uint32		flags = 0;
2534 
2535 	if (fDraft) {
2536 		if ((status = draft.SetTo(fRef,
2537 			B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE)) != B_OK) {
2538 			return status;
2539 		}
2540 	} else {
2541 		// Get the user home directory
2542 		if ((status = find_directory(B_USER_DIRECTORY, &draftPath)) != B_OK)
2543 			return status;
2544 
2545 		// Append the relative path of the draft directory
2546 		draftPath.Append(kDraftPath);
2547 
2548 		// Create the file
2549 		status = dir.SetTo(draftPath.Path());
2550 		switch (status) {
2551 			// Create the directory if it does not exist
2552 			case B_ENTRY_NOT_FOUND:
2553 				if ((status = dir.CreateDirectory(draftPath.Path(), &dir))
2554 					!= B_OK)
2555 					return status;
2556 			case B_OK:
2557 			{
2558 				char fileName[B_FILE_NAME_LENGTH];
2559 				// save as some version of the message's subject
2560 				if (strlen(fHeaderView->fSubject->Text()) == 0)
2561 					strlcpy(fileName, B_TRANSLATE("Untitled"),
2562 						sizeof(fileName));
2563 				else
2564 					strlcpy(fileName, fHeaderView->fSubject->Text(),
2565 						sizeof(fileName));
2566 
2567 				uint32 originalLength = strlen(fileName);
2568 
2569 				// convert /, \ and : to -
2570 				for (char *bad = fileName; (bad = strchr(bad, '/')) != NULL;
2571 					++bad) *bad = '-';
2572 				for (char *bad = fileName; (bad = strchr(bad, '\\')) != NULL;
2573 					++bad) *bad = '-';
2574 				for (char *bad = fileName; (bad = strchr(bad, ':')) != NULL;
2575 					++bad) *bad = '-';
2576 
2577 				// Create the file; if the name exists, find a unique name
2578 				flags = B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS;
2579 				int32 i = 1;
2580 				do {
2581 					status = draft.SetTo(&dir, fileName, flags);
2582 					if (status == B_OK)
2583 						break;
2584 					char appendix[B_FILE_NAME_LENGTH];
2585 					sprintf(appendix, " %" B_PRId32, i++);
2586 					int32 pos = min_c(sizeof(fileName) - strlen(appendix),
2587 						originalLength);
2588 					sprintf(fileName + pos, "%s", appendix);
2589 				} while (status == B_FILE_EXISTS);
2590 				if (status != B_OK)
2591 					return status;
2592 
2593 				// Cache the ref
2594 				if (fRef == NULL)
2595 					fRef = new entry_ref;
2596 				BEntry entry(&dir, fileName);
2597 				entry.GetRef(fRef);
2598 				break;
2599 			}
2600 			default:
2601 				return status;
2602 		}
2603 	}
2604 
2605 	// Write the content of the message
2606 	draft.Write(fContentView->fTextView->Text(),
2607 		fContentView->fTextView->TextLength());
2608 
2609 	//
2610 	// Add the header stuff as attributes
2611 	//
2612 	WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->fTo->Text());
2613 	WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->fTo->Text());
2614 	WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->fSubject->Text());
2615 	if (fHeaderView->fCc != NULL)
2616 		WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->fCc->Text());
2617 	if (fHeaderView->fBcc != NULL)
2618 		WriteAttrString(&draft, B_MAIL_ATTR_BCC, fHeaderView->fBcc->Text());
2619 
2620 	// Add account
2621 	BMenuItem* menuItem = fHeaderView->fAccountMenu->FindMarked();
2622 	if (menuItem != NULL)
2623 		WriteAttrString(&draft, B_MAIL_ATTR_ACCOUNT, menuItem->Label());
2624 
2625 	// Add encoding
2626 	menuItem = fHeaderView->fEncodingMenu->FindMarked();
2627 	if (menuItem != NULL)
2628 		WriteAttrString(&draft, "MAIL:encoding", menuItem->Label());
2629 
2630 	// Add the draft attribute for indexing
2631 	uint32 draftAttr = true;
2632 	draft.WriteAttr("MAIL:draft", B_INT32_TYPE, 0, &draftAttr, sizeof(uint32));
2633 
2634 	// Add Attachment paths in attribute
2635 	if (fEnclosuresView != NULL) {
2636 		TListItem *item;
2637 		BPath path;
2638 		BString pathStr;
2639 
2640 		for (int32 i = 0; (item = (TListItem *)
2641 			fEnclosuresView->fList->ItemAt(i)) != NULL; i++) {
2642 			if (i > 0)
2643 				pathStr.Append(":");
2644 
2645 			BEntry entry(item->Ref(), true);
2646 			if (!entry.Exists())
2647 				continue;
2648 
2649 			entry.GetPath(&path);
2650 			pathStr.Append(path.Path());
2651 		}
2652 		if (pathStr.Length())
2653 			draft.WriteAttrString("MAIL:attachments", &pathStr);
2654 	}
2655 
2656 	// Set the MIME Type of the file
2657 	BNodeInfo info(&draft);
2658 	info.SetType(kDraftType);
2659 
2660 	fDraft = true;
2661 	fChanged = false;
2662 
2663 	fSaveButton->SetEnabled(false);
2664 
2665 	return B_OK;
2666 }
2667 
2668 
2669 status_t
2670 TMailWindow::TrainMessageAs(const char *CommandWord)
2671 {
2672 	status_t	errorCode = -1;
2673 	char		errorString[1500];
2674 	BEntry		fileEntry;
2675 	BPath		filePath;
2676 	BMessage	replyMessage;
2677 	BMessage	scriptingMessage;
2678 	team_id		serverTeam;
2679 
2680 	if (fRef == NULL)
2681 		goto ErrorExit; // Need to have a real file and name.
2682 	errorCode = fileEntry.SetTo(fRef, true /* traverse */);
2683 	if (errorCode != B_OK)
2684 		goto ErrorExit;
2685 	errorCode = fileEntry.GetPath(&filePath);
2686 	if (errorCode != B_OK)
2687 		goto ErrorExit;
2688 	fileEntry.Unset();
2689 
2690 	// Get a connection to the spam database server.  Launch if needed.
2691 
2692 	if (!fMessengerToSpamServer.IsValid()) {
2693 		// Make sure the server is running.
2694 		if (!be_roster->IsRunning (kSpamServerSignature)) {
2695 			errorCode = be_roster->Launch (kSpamServerSignature);
2696 			if (errorCode != B_OK) {
2697 				BPath path;
2698 				entry_ref ref;
2699 				directory_which places[]
2700 					= {B_SYSTEM_NONPACKAGED_BIN_DIRECTORY, B_SYSTEM_BIN_DIRECTORY};
2701 				for (int32 i = 0; i < 2; i++) {
2702 					find_directory(places[i],&path);
2703 					path.Append("spamdbm");
2704 					if (!BEntry(path.Path()).Exists())
2705 						continue;
2706 					get_ref_for_path(path.Path(),&ref);
2707 					if ((errorCode =  be_roster->Launch (&ref)) == B_OK)
2708 						break;
2709 				}
2710 				if (errorCode != B_OK)
2711 					goto ErrorExit;
2712 			}
2713 		}
2714 
2715 		// Set up the messenger to the database server.
2716 		errorCode = B_SERVER_NOT_FOUND;
2717 		serverTeam = be_roster->TeamFor(kSpamServerSignature);
2718 		if (serverTeam < 0)
2719 			goto ErrorExit;
2720 
2721 		fMessengerToSpamServer = BMessenger (kSpamServerSignature, serverTeam,
2722 			&errorCode);
2723 
2724 		if (!fMessengerToSpamServer.IsValid())
2725 			goto ErrorExit;
2726 	}
2727 
2728 	// Ask the server to train on the message.  Give it the command word and
2729 	// the absolute path name to use.
2730 
2731 	scriptingMessage.MakeEmpty();
2732 	scriptingMessage.what = B_SET_PROPERTY;
2733 	scriptingMessage.AddSpecifier(CommandWord);
2734 	errorCode = scriptingMessage.AddData("data", B_STRING_TYPE,
2735 		filePath.Path(), strlen(filePath.Path()) + 1, false /* fixed size */);
2736 	if (errorCode != B_OK)
2737 		goto ErrorExit;
2738 	replyMessage.MakeEmpty();
2739 	errorCode = fMessengerToSpamServer.SendMessage(&scriptingMessage,
2740 		&replyMessage);
2741 	if (errorCode != B_OK
2742 		|| replyMessage.FindInt32("error", &errorCode) != B_OK
2743 		|| errorCode != B_OK)
2744 		goto ErrorExit; // Classification failed in one of many ways.
2745 
2746 	SetTitleForMessage();
2747 		// Update window title to show new spam classification.
2748 	return B_OK;
2749 
2750 ErrorExit:
2751 	beep();
2752 	sprintf(errorString, "Unable to train the message file \"%s\" as %s.  "
2753 		"Possibly useful error code: %s (%" B_PRId32 ").",
2754 		filePath.Path(), CommandWord, strerror(errorCode), errorCode);
2755 	BAlert* alert = new BAlert("", errorString,	B_TRANSLATE("OK"));
2756 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2757 	alert->Go();
2758 
2759 	return errorCode;
2760 }
2761 
2762 
2763 void
2764 TMailWindow::SetTitleForMessage()
2765 {
2766 	//
2767 	//	Figure out the title of this message and set the title bar
2768 	//
2769 	BString title = B_TRANSLATE_SYSTEM_NAME("Mail");
2770 
2771 	if (fIncoming) {
2772 		if (fMail->GetName(&title) == B_OK)
2773 			title << ": \"" << fMail->Subject() << "\"";
2774 		else
2775 			title = fMail->Subject();
2776 
2777 		if (fDownloading)
2778 			title.Prepend("Downloading: ");
2779 
2780 		if (fApp->ShowSpamGUI() && fRef != NULL) {
2781 			BString	classification;
2782 			BNode	node (fRef);
2783 			char	numberString [30];
2784 			BString oldTitle (title);
2785 			float	spamRatio;
2786 			if (node.InitCheck() != B_OK || node.ReadAttrString
2787 				("MAIL:classification", &classification) != B_OK)
2788 				classification = "Unrated";
2789 			if (classification != "Spam" && classification != "Genuine") {
2790 				// Uncertain, Unrated and other unknown classes, show the ratio.
2791 				if (node.InitCheck() == B_OK && sizeof (spamRatio) ==
2792 					node.ReadAttr("MAIL:ratio_spam", B_FLOAT_TYPE, 0,
2793 					&spamRatio, sizeof (spamRatio))) {
2794 					sprintf (numberString, "%.4f", spamRatio);
2795 					classification << " " << numberString;
2796 				}
2797 			}
2798 			title = "";
2799 			title << "[" << classification << "] " << oldTitle;
2800 		}
2801 	}
2802 	SetTitle(title);
2803 }
2804 
2805 
2806 //
2807 //	Open *another* message in the existing mail window.  Some code here is
2808 //	duplicated from various constructors.
2809 //	The duplicated code should be in a private initializer method -- axeld.
2810 //
2811 
2812 status_t
2813 TMailWindow::OpenMessage(const entry_ref *ref, uint32 characterSetForDecoding)
2814 {
2815 	if (ref == NULL)
2816 		return B_ERROR;
2817 	//
2818 	//	Set some references to the email file
2819 	//
2820 	delete fRef;
2821 	fRef = new entry_ref(*ref);
2822 
2823 	fPrevTrackerPositionSaved = false;
2824 	fNextTrackerPositionSaved = false;
2825 
2826 	fContentView->fTextView->StopLoad();
2827 	delete fMail;
2828 	fMail = NULL;
2829 
2830 	BFile file(fRef, B_READ_ONLY);
2831 	status_t err = file.InitCheck();
2832 	if (err != B_OK)
2833 		return err;
2834 
2835 	char mimeType[256];
2836 	BNodeInfo fileInfo(&file);
2837 	fileInfo.GetType(mimeType);
2838 
2839 	if (strcmp(mimeType, B_PARTIAL_MAIL_TYPE) == 0) {
2840 		BMessenger listener(this);
2841 		BMailDaemon().FetchBody(*ref, &listener);
2842 		fileInfo.GetType(mimeType);
2843 		_SetDownloading(true);
2844 	} else
2845 		_SetDownloading(false);
2846 
2847 	// Check if it's a draft file, which contains only the text, and has the
2848 	// from, to, bcc, attachments listed as attributes.
2849 	if (strcmp(kDraftType, mimeType) == 0) {
2850 		BNode node(fRef);
2851 		off_t size;
2852 		BString string;
2853 
2854 		fMail = new BEmailMessage; // Not really used much, but still needed.
2855 
2856 		// Load the raw UTF-8 text from the file.
2857 		file.GetSize(&size);
2858 		fContentView->fTextView->SetText(&file, 0, size);
2859 
2860 		// Restore Fields from attributes
2861 		if (node.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK)
2862 			fHeaderView->fTo->SetText(string.String());
2863 		if (node.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK)
2864 			fHeaderView->fSubject->SetText(string.String());
2865 		if (node.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK)
2866 			fHeaderView->fCc->SetText(string.String());
2867 		if (node.ReadAttrString(B_MAIL_ATTR_BCC, &string) == B_OK)
2868 			fHeaderView->fBcc->SetText(string.String());
2869 
2870 		// Restore account
2871 		if (node.ReadAttrString(B_MAIL_ATTR_ACCOUNT, &string) == B_OK) {
2872 			BMenuItem* accountItem = fHeaderView->fAccountMenu->FindItem(string.String());
2873 			if (accountItem != NULL)
2874 				accountItem->SetMarked(true);
2875 		}
2876 
2877 		// Restore encoding
2878 		if (node.ReadAttrString("MAIL:encoding", &string) == B_OK) {
2879 			BMenuItem* encodingItem = fHeaderView->fEncodingMenu->FindItem(string.String());
2880 			if (encodingItem != NULL)
2881 				encodingItem->SetMarked(true);
2882 		}
2883 
2884 		// Restore attachments
2885 		if (node.ReadAttrString("MAIL:attachments", &string) == B_OK) {
2886 			BMessage msg(REFS_RECEIVED);
2887 			entry_ref enc_ref;
2888 
2889 			char *s = strtok((char *)string.String(), ":");
2890 			while (s) {
2891 				BEntry entry(s, true);
2892 				if (entry.Exists()) {
2893 					entry.GetRef(&enc_ref);
2894 					msg.AddRef("refs", &enc_ref);
2895 				}
2896 				s = strtok(NULL, ":");
2897 			}
2898 			AddEnclosure(&msg);
2899 		}
2900 
2901 		// restore the reading position if available
2902 		PostMessage(M_READ_POS);
2903 
2904 		PostMessage(RESET_BUTTONS);
2905 		fIncoming = false;
2906 		fDraft = true;
2907 	} else {
2908 		// A real mail message, parse its headers to get from, to, etc.
2909 		fMail = new BEmailMessage(fRef, characterSetForDecoding);
2910 		fIncoming = true;
2911 		fHeaderView->LoadMessage(fMail);
2912 	}
2913 
2914 	err = fMail->InitCheck();
2915 	if (err < B_OK) {
2916 		delete fMail;
2917 		fMail = NULL;
2918 		return err;
2919 	}
2920 
2921 	SetTitleForMessage();
2922 
2923 	if (fIncoming) {
2924 		//
2925 		//	Put the addresses in the 'Save Address' Menu
2926 		//
2927 		BMenuItem *item;
2928 		while ((item = fSaveAddrMenu->RemoveItem((int32)0)) != NULL)
2929 			delete item;
2930 
2931 		// create the list of addresses
2932 
2933 		BList addressList;
2934 		get_address_list(addressList, fMail->To(), extract_address);
2935 		get_address_list(addressList, fMail->CC(), extract_address);
2936 		get_address_list(addressList, fMail->From(), extract_address);
2937 		get_address_list(addressList, fMail->ReplyTo(), extract_address);
2938 
2939 		BMessage *msg;
2940 
2941 		for (int32 i = addressList.CountItems(); i-- > 0;) {
2942 			char *address = (char *)addressList.RemoveItem((int32)0);
2943 
2944 			// insert the new address in alphabetical order
2945 			int32 index = 0;
2946 			while ((item = fSaveAddrMenu->ItemAt(index)) != NULL) {
2947 				if (!strcmp(address, item->Label())) {
2948 					// item already in list
2949 					goto skip;
2950 				}
2951 
2952 				if (strcmp(address, item->Label()) < 0)
2953 					break;
2954 
2955 				index++;
2956 			}
2957 
2958 			msg = new BMessage(M_SAVE);
2959 			msg->AddString("address", address);
2960 			fSaveAddrMenu->AddItem(new BMenuItem(address, msg), index);
2961 
2962 		skip:
2963 			free(address);
2964 		}
2965 
2966 		//
2967 		// Clear out existing contents of text view.
2968 		//
2969 		fContentView->fTextView->SetText("", (int32)0);
2970 
2971 		fContentView->fTextView->LoadMessage(fMail, false, NULL);
2972 
2973 		if (fApp->ShowButtonBar())
2974 			_UpdateReadButton();
2975 	}
2976 
2977 	return B_OK;
2978 }
2979 
2980 
2981 TMailWindow *
2982 TMailWindow::FrontmostWindow()
2983 {
2984 	BAutolock locker(sWindowListLock);
2985 	if (sWindowList.CountItems() > 0)
2986 		return (TMailWindow *)sWindowList.ItemAt(0);
2987 
2988 	return NULL;
2989 }
2990 
2991 
2992 /*
2993 // Copied from src/kits/tracker/FindPanel.cpp.
2994 uint32
2995 TMailWindow::InitialMode(const BNode *node)
2996 {
2997 	if (!node || node->InitCheck() != B_OK)
2998 		return kByNameItem;
2999 
3000 	uint32 result;
3001 	if (node->ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0,
3002 		(int32 *)&result, sizeof(int32)) <= 0)
3003 		return kByNameItem;
3004 
3005 	return result;
3006 }
3007 
3008 
3009 // Copied from src/kits/tracker/FindPanel.cpp.
3010 int32
3011 TMailWindow::InitialAttrCount(const BNode *node)
3012 {
3013 	if (!node || node->InitCheck() != B_OK)
3014 		return 1;
3015 
3016 	int32 result;
3017 	if (node->ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0,
3018 		&result, sizeof(int32)) <= 0)
3019 		return 1;
3020 
3021 	return result;
3022 }*/
3023 
3024 
3025 // #pragma mark -
3026 
3027 
3028 void
3029 TMailWindow::_UpdateSizeLimits()
3030 {
3031 	float minWidth, maxWidth, minHeight, maxHeight;
3032 	GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
3033 
3034 	float height;
3035 	fMenuBar->GetPreferredSize(&minWidth, &height);
3036 
3037 	minHeight = height;
3038 
3039 	if (fButtonBar) {
3040 		fButtonBar->GetPreferredSize(&minWidth, &height);
3041 		minHeight += height;
3042 	} else {
3043 		minWidth = WIND_WIDTH;
3044 	}
3045 
3046 	minHeight += fHeaderView->Bounds().Height() + ENCLOSURES_HEIGHT + 60;
3047 
3048 	SetSizeLimits(minWidth, RIGHT_BOUNDARY, minHeight, RIGHT_BOUNDARY);
3049 }
3050 
3051 
3052 status_t
3053 TMailWindow::_GetQueryPath(BPath* queryPath) const
3054 {
3055 	// get the user home directory and from there the query folder
3056 	status_t ret = find_directory(B_USER_DIRECTORY, queryPath);
3057 	if (ret == B_OK)
3058 		ret = queryPath->Append(kQueriesDirectory);
3059 
3060 	return ret;
3061 }
3062 
3063 
3064 void
3065 TMailWindow::_RebuildQueryMenu(bool firstTime)
3066 {
3067 	while (fQueryMenu->ItemAt(0)) {
3068 		BMenuItem* item = fQueryMenu->RemoveItem((int32)0);
3069 		delete item;
3070 	}
3071 
3072 	fQueryMenu->AddItem(new BMenuItem(B_TRANSLATE("Edit queries"
3073 			B_UTF8_ELLIPSIS),
3074 		new BMessage(M_EDIT_QUERIES), 'E', B_SHIFT_KEY));
3075 
3076 	bool queryItemsAdded = false;
3077 
3078 	BPath queryPath;
3079 	if (_GetQueryPath(&queryPath) < B_OK)
3080 		return;
3081 
3082 	BDirectory queryDir(queryPath.Path());
3083 
3084 	if (firstTime) {
3085 		BPrivate::BPathMonitor::StartWatching(queryPath.Path(),
3086 			B_WATCH_RECURSIVELY, BMessenger(this, this));
3087 	}
3088 
3089 	// If we find the named query, add it to the menu.
3090 	BEntry entry;
3091 	while (queryDir.GetNextEntry(&entry) == B_OK) {
3092 		char name[B_FILE_NAME_LENGTH + 1];
3093 		entry.GetName(name);
3094 
3095 		char* queryString = _BuildQueryString(&entry);
3096 		if (queryString == NULL)
3097 			continue;
3098 
3099 		queryItemsAdded = true;
3100 
3101 		QueryMenu* queryMenu = new QueryMenu(name, false);
3102 		queryMenu->SetTargetForItems(be_app);
3103 		queryMenu->SetPredicate(queryString);
3104 		fQueryMenu->AddItem(queryMenu);
3105 
3106 		free(queryString);
3107 	}
3108 
3109 	if (queryItemsAdded)
3110 		fQueryMenu->AddItem(new BSeparatorItem(), 1);
3111 }
3112 
3113 
3114 char*
3115 TMailWindow::_BuildQueryString(BEntry* entry) const
3116 {
3117 	BNode node(entry);
3118 	if (node.InitCheck() != B_OK)
3119 		return NULL;
3120 
3121 	uint32 mode;
3122 	if (node.ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, (int32*)&mode,
3123 		sizeof(int32)) <= 0) {
3124 		mode = kByNameItem;
3125 	}
3126 
3127 	BString queryString;
3128 	switch (mode) {
3129 		case kByForumlaItem:
3130 		{
3131 			BString buffer;
3132 			if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK)
3133 				queryString << buffer;
3134 			break;
3135 		}
3136 
3137 		case kByNameItem:
3138 		{
3139 			BString buffer;
3140 			if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK)
3141 				queryString << "(name==*" << buffer << "*)";
3142 			break;
3143 		}
3144 
3145 		case kByAttributeItem:
3146 		{
3147 			int32 count = 1;
3148 			if (node.ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0,
3149 					(int32 *)&count, sizeof(int32)) <= 0) {
3150 				count = 1;
3151 			}
3152 
3153 			attr_info info;
3154 			if (node.GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK)
3155 				break;
3156 
3157 			if (count > 1)
3158 				queryString << "(";
3159 
3160 			char *buffer = new char[info.size];
3161 			if (node.ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0,
3162 					buffer, (size_t)info.size) == info.size) {
3163 				BMessage message;
3164 				if (message.Unflatten(buffer) == B_OK) {
3165 					for (int32 index = 0; /*index < count*/; index++) {
3166 						const char *field;
3167 						const char *value;
3168 						if (message.FindString("menuSelection", index, &field)
3169 								!= B_OK
3170 							|| message.FindString("attrViewText", index, &value)
3171 								!= B_OK) {
3172 							break;
3173 						}
3174 
3175 						// ignore the mime type, we'll force it to be email
3176 						// later
3177 						if (strcmp(field, "BEOS:TYPE") != 0) {
3178 							// TODO: check if subMenu contains the type of
3179 							// comparison we are suppose to make here
3180 							queryString << "(" << field << "==\""
3181 								<< value << "\")";
3182 
3183 							int32 logicMenuSelectedIndex;
3184 							if (message.FindInt32("logicalRelation", index,
3185 								&logicMenuSelectedIndex) == B_OK) {
3186 								if (logicMenuSelectedIndex == 0)
3187 									queryString << "&&";
3188 								else if (logicMenuSelectedIndex == 1)
3189 									queryString << "||";
3190 							} else
3191 								break;
3192 						}
3193 					}
3194 				}
3195 			}
3196 
3197 			if (count > 1)
3198 				queryString << ")";
3199 
3200 			delete [] buffer;
3201 			break;
3202 		}
3203 
3204 		default:
3205 			break;
3206 	}
3207 
3208 	if (queryString.Length() == 0)
3209 		return NULL;
3210 
3211 	// force it to check for email only
3212 	if (queryString.FindFirst("text/x-email") < 0) {
3213 		BString temp;
3214 		temp << "(" << queryString << "&&(BEOS:TYPE==\"text/x-email\"))";
3215 		queryString = temp;
3216 	}
3217 
3218 	return strdup(queryString.String());
3219 }
3220 
3221 
3222 void
3223 TMailWindow::_AddReadButton()
3224 {
3225 	BNode node(fRef);
3226 
3227 	read_flags flag = B_UNREAD;
3228 	read_read_attr(node, flag);
3229 
3230 	int32 buttonIndex = fButtonBar->IndexOf(fNextButton);
3231 	if (flag == B_READ) {
3232 		fReadButton = fButtonBar->AddButton(B_TRANSLATE("Unread"), 28,
3233 			new BMessage(M_UNREAD), buttonIndex);
3234 	} else {
3235 		fReadButton = fButtonBar->AddButton(B_TRANSLATE(" Read "), 24,
3236 			new BMessage(M_READ), buttonIndex);
3237 	}
3238 }
3239 
3240 
3241 void
3242 TMailWindow::_UpdateReadButton()
3243 {
3244 	if (fApp->ShowButtonBar()) {
3245 		fButtonBar->RemoveButton(fReadButton);
3246 		fReadButton = NULL;
3247 		if (!fAutoMarkRead && fIncoming)
3248 			_AddReadButton();
3249 	}
3250 	UpdateViews();
3251 }
3252 
3253 
3254 void
3255 TMailWindow::_SetDownloading(bool downloading)
3256 {
3257 	fDownloading = downloading;
3258 }
3259