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