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