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