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