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