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