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