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