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