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