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