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