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