xref: /haiku/src/apps/mail/MailApp.cpp (revision 125183f9e5c136781f71c879faaeab43fdc3ea7b)
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 "MailApp.h"
37 
38 #include <fcntl.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sys/stat.h>
43 #include <sys/utsname.h>
44 #include <unistd.h>
45 
46 #include <Autolock.h>
47 #include <Catalog.h>
48 #include <Clipboard.h>
49 #include <Debug.h>
50 #include <E-mail.h>
51 #include <InterfaceKit.h>
52 #include <Locale.h>
53 #include <Roster.h>
54 #include <Screen.h>
55 #include <StorageKit.h>
56 #include <String.h>
57 #include <TextView.h>
58 #include <UTF8.h>
59 
60 #include <fs_index.h>
61 #include <fs_info.h>
62 
63 #include <MailMessage.h>
64 #include <MailSettings.h>
65 #include <MailDaemon.h>
66 #include <mail_util.h>
67 
68 #include <CharacterSetRoster.h>
69 
70 using namespace BPrivate ;
71 
72 #include "ButtonBar.h"
73 #include "Content.h"
74 #include "Enclosures.h"
75 #include "FieldMsg.h"
76 #include "FindWindow.h"
77 #include "Header.h"
78 #include "MailSupport.h"
79 #include "MailWindow.h"
80 #include "Messages.h"
81 #include "Prefs.h"
82 #include "QueryMenu.h"
83 #include "Signature.h"
84 #include "Status.h"
85 #include "String.h"
86 #include "Utilities.h"
87 #include "Words.h"
88 
89 
90 #define TR_CONTEXT "Mail"
91 
92 
93 static const char *kDictDirectory = "word_dictionary";
94 static const char *kIndexDirectory = "word_index";
95 static const char *kWordsPath = "/boot/optional/goodies/words";
96 static const char *kExact = ".exact";
97 static const char *kMetaphone = ".metaphone";
98 
99 
100 TMailApp::TMailApp()
101 	:
102 	BApplication("application/x-vnd.Be-MAIL"),
103 	fWindowCount(0),
104 	fPrefsWindow(NULL),
105 	fSigWindow(NULL),
106 
107 	fPrintSettings(NULL),
108 	fPrintHelpAndExit(false),
109 
110 	fWrapMode(true),
111 	fAttachAttributes(true),
112 	fColoredQuotes(true),
113 	fShowButtonBar(true),
114 	fWarnAboutUnencodableCharacters(true),
115 	fStartWithSpellCheckOn(false),
116 	fShowSpamGUI(true),
117 	fMailCharacterSet(B_MS_WINDOWS_CONVERSION),
118 	fContentFont(be_fixed_font)
119 {
120 	be_locale->GetAppCatalog(&fCatalog);
121 
122 	// set default values
123 	fContentFont.SetSize(12.0);
124 	fAutoMarkRead = true;
125 	fSignature = (char*)malloc(strlen(B_TRANSLATE("None")) + 1);
126 	strcpy(fSignature, B_TRANSLATE("None"));
127 	fReplyPreamble = (char*)malloc(1);
128 	fReplyPreamble[0] = '\0';
129 
130 	fMailWindowFrame.Set(0, 0, 0, 0);
131 	fSignatureWindowFrame.Set(6, TITLE_BAR_HEIGHT, 6 + kSigWidth,
132 		TITLE_BAR_HEIGHT + kSigHeight);
133 	fPrefsWindowPos.Set(6, TITLE_BAR_HEIGHT);
134 
135 	// Find and read settings file.
136 	LoadSettings();
137 
138 	_CheckForSpamFilterExistence();
139 	fContentFont.SetSpacing(B_BITMAP_SPACING);
140 	fLastMailWindowFrame = fMailWindowFrame;
141 }
142 
143 
144 TMailApp::~TMailApp()
145 {
146 }
147 
148 
149 void
150 TMailApp::AboutRequested()
151 {
152 	BAlert *alert = new BAlert("about", B_TRANSLATE("Mail\n\n"
153 		"Written by Robert Polic.\n"
154 		"Enhanced by the Dr. Zoidberg crew.\n\n"
155 		"Copyright 1991-2001, Be Incorporated.\n"
156 		"Copyright 2005-2010 Haiku, Inc.\n"), "OK");
157 	BTextView *view = alert->TextView();
158 	BFont font;
159 
160 	view->SetStylable(true);
161 
162 	view->GetFont(&font);
163 	font.SetSize(font.Size() + 7.0f);
164 	font.SetFace(B_BOLD_FACE);
165 	view->SetFontAndColor(0, 4, &font);
166 
167 	alert->Go();
168 }
169 
170 
171 void
172 TMailApp::ArgvReceived(int32 argc, char **argv)
173 {
174 	BEntry entry;
175 	BString names;
176 	BString ccNames;
177 	BString bccNames;
178 	BString subject;
179 	BString body;
180 	BMessage enclosure(B_REFS_RECEIVED);
181 	// a "mailto:" with no name should open an empty window
182 	// so remember if we got a "mailto:" even if there isn't a name
183 	// that goes along with it (this allows deskbar replicant to open
184 	// an empty message even when Mail is already running)
185 	bool gotmailto = false;
186 
187 	for (int32 loop = 1; loop < argc; loop++)
188 	{
189 		if (strcmp(argv[loop], "-h") == 0
190 			|| strcmp(argv[loop], "--help") == 0)
191 		{
192 			printf(" usage: %s [ mailto:<address> ] [ -subject \"<text>\" ] [ ccto:<address> ] [ bccto:<address> ] "
193 				"[ -body \"<body text\" ] [ enclosure:<path> ] [ <message to read> ...] \n",
194 				argv[0]);
195 			fPrintHelpAndExit = true;
196 			be_app->PostMessage(B_QUIT_REQUESTED);
197 			return;
198 		}
199 		else if (strncmp(argv[loop], "mailto:", 7) == 0)
200 		{
201 			if (names.Length())
202 				names += ", ";
203 			char *options;
204 			if ((options = strchr(argv[loop],'?')) != NULL)
205 			{
206 				names.Append(argv[loop] + 7, options - argv[loop] - 7);
207 				if (!strncmp(++options,"subject=",8))
208 					subject = options + 8;
209 			}
210 			else
211 				names += argv[loop] + 7;
212 			gotmailto = true;
213 		}
214 		else if (strncmp(argv[loop], "ccto:", 5) == 0)
215 		{
216 			if (ccNames.Length())
217 				ccNames += ", ";
218 			ccNames += argv[loop] + 5;
219 		}
220 		else if (strncmp(argv[loop], "bccto:", 6) == 0)
221 		{
222 			if (bccNames.Length())
223 				bccNames += ", ";
224 			bccNames += argv[loop] + 6;
225 		}
226 		else if (strcmp(argv[loop], "-subject") == 0)
227 			subject = argv[++loop];
228 		else if (strcmp(argv[loop], "-body") == 0 && argv[loop + 1])
229 			body = argv[++loop];
230 		else if (strncmp(argv[loop], "enclosure:", 10) == 0)
231 		{
232 			BEntry tmp(argv[loop] + 10, true);
233 			if (tmp.InitCheck() == B_OK && tmp.Exists())
234 			{
235 				entry_ref ref;
236 				tmp.GetRef(&ref);
237 				enclosure.AddRef("refs", &ref);
238 			}
239 		}
240 		else if (entry.SetTo(argv[loop]) == B_NO_ERROR)
241 		{
242 			BMessage msg(B_REFS_RECEIVED);
243 			entry_ref ref;
244 			entry.GetRef(&ref);
245 			msg.AddRef("refs", &ref);
246 			RefsReceived(&msg);
247 		}
248 	}
249 
250 	if (gotmailto || names.Length() || ccNames.Length() || bccNames.Length() || subject.Length()
251 		|| body.Length() || enclosure.HasRef("refs"))
252 	{
253 		TMailWindow	*window = NewWindow(NULL, names.String());
254 		window->SetTo(names.String(), subject.String(), ccNames.String(), bccNames.String(),
255 			&body, &enclosure);
256 		window->Show();
257 	}
258 }
259 
260 
261 void
262 TMailApp::MessageReceived(BMessage *msg)
263 {
264 	TMailWindow	*window = NULL;
265 	entry_ref ref;
266 
267 	switch (msg->what) {
268 		case M_NEW:
269 		{
270 			int32 type;
271 			msg->FindInt32("type", &type);
272 			switch (type) {
273 				case M_NEW:
274 					window = NewWindow();
275 					break;
276 
277 				case M_RESEND:
278 				{
279 					msg->FindRef("ref", &ref);
280 					BNode file(&ref);
281 					BString string = "";
282 
283 					if (file.InitCheck() == B_OK)
284 						ReadAttrString(&file, B_MAIL_ATTR_TO, &string);
285 
286 					window = NewWindow(&ref, string.String(), true);
287 					break;
288 				}
289 				case M_FORWARD:
290 				case M_FORWARD_WITHOUT_ATTACHMENTS:
291 				{
292 					TMailWindow	*sourceWindow;
293 					if (msg->FindPointer("window", (void **)&sourceWindow) < B_OK
294 						|| !sourceWindow->Lock())
295 						break;
296 
297 					msg->FindRef("ref", &ref);
298 					window = NewWindow();
299 					if (window->Lock()) {
300 						window->Forward(&ref, sourceWindow, type == M_FORWARD);
301 						window->Unlock();
302 					}
303 					sourceWindow->Unlock();
304 					break;
305 				}
306 
307 				case M_REPLY:
308 				case M_REPLY_TO_SENDER:
309 				case M_REPLY_ALL:
310 				case M_COPY_TO_NEW:
311 				{
312 					TMailWindow	*sourceWindow;
313 					if (msg->FindPointer("window", (void **)&sourceWindow) < B_OK
314 						|| !sourceWindow->Lock())
315 						break;
316 					msg->FindRef("ref", &ref);
317 					window = NewWindow();
318 					if (window->Lock()) {
319 						if (type == M_COPY_TO_NEW)
320 							window->CopyMessage(&ref, sourceWindow);
321 						else
322 							window->Reply(&ref, sourceWindow, type);
323 						window->Unlock();
324 					}
325 					sourceWindow->Unlock();
326 					break;
327 				}
328 			}
329 			if (window)
330 				window->Show();
331 			break;
332 		}
333 
334 		case M_PREFS:
335 			if (fPrefsWindow)
336 				fPrefsWindow->Activate(true);
337 			else {
338 				fPrefsWindow = new TPrefsWindow(BRect(fPrefsWindowPos.x,
339 						fPrefsWindowPos.y, fPrefsWindowPos.x + PREF_WIDTH,
340 						fPrefsWindowPos.y + PREF_HEIGHT),
341 						&fContentFont, NULL, &fWrapMode, &fAttachAttributes,
342 						&fColoredQuotes, &fDefaultChain, &fUseAccountFrom,
343 						&fReplyPreamble, &fSignature, &fMailCharacterSet,
344 						&fWarnAboutUnencodableCharacters,
345 						&fStartWithSpellCheckOn, &fAutoMarkRead,
346 						&fShowButtonBar);
347 				fPrefsWindow->Show();
348 			}
349 			break;
350 
351 		case PREFS_CHANGED:
352 		{
353 			// Notify all Mail windows
354 			TMailWindow	*window;
355 			for (int32 i = 0; (window=(TMailWindow *)fWindowList.ItemAt(i))
356 				!= NULL; i++)
357 			{
358 				window->Lock();
359 				window->UpdatePreferences();
360 				window->UpdateViews();
361 				window->Unlock();
362 			}
363 			break;
364 		}
365 
366 		case M_ACCOUNTS:
367 			be_roster->Launch("application/x-vnd.Haiku-Mail");
368 			break;
369 
370 		case M_EDIT_SIGNATURE:
371 			if (fSigWindow)
372 				fSigWindow->Activate(true);
373 			else {
374 				fSigWindow = new TSignatureWindow(fSignatureWindowFrame);
375 				fSigWindow->Show();
376 			}
377 			break;
378 
379 		case M_FONT:
380 			FontChange();
381 			break;
382 
383 		case REFS_RECEIVED:
384 			if (msg->HasPointer("window")) {
385 				msg->FindPointer("window", (void **)&window);
386 				BMessage message(*msg);
387 				window->PostMessage(&message, window);
388 			}
389 			break;
390 
391 		case WINDOW_CLOSED:
392 			switch (msg->FindInt32("kind")) {
393 				case MAIL_WINDOW:
394 				{
395 					TMailWindow	*window;
396 					if( msg->FindPointer( "window", (void **)&window ) == B_OK )
397 						fWindowList.RemoveItem(window);
398 					fWindowCount--;
399 					break;
400 				}
401 
402 				case PREFS_WINDOW:
403 					fPrefsWindow = NULL;
404 					msg->FindPoint("window pos", &fPrefsWindowPos);
405 					break;
406 
407 				case SIG_WINDOW:
408 					fSigWindow = NULL;
409 					msg->FindRect("window frame", &fSignatureWindowFrame);
410 					break;
411 			}
412 
413 			if (!fWindowCount && !fSigWindow && !fPrefsWindow)
414 				be_app->PostMessage(B_QUIT_REQUESTED);
415 			break;
416 
417 		case B_REFS_RECEIVED:
418 			RefsReceived(msg);
419 			break;
420 
421 		case B_PRINTER_CHANGED:
422 			_ClearPrintSettings();
423 			break;
424 
425 		default:
426 			BApplication::MessageReceived(msg);
427 	}
428 }
429 
430 
431 bool
432 TMailApp::QuitRequested()
433 {
434 	if (!BApplication::QuitRequested())
435 		return false;
436 
437     fMailWindowFrame = fLastMailWindowFrame;
438     	// Last closed window becomes standard window size.
439 
440 	// Shut down the spam server if it's still running. If the user has trained it on a message, it will stay
441 	// open. This is actually a good thing if there's quite a bit of spam -- no waiting for the thing to start
442 	// up for each message, but it has no business staying that way if the user isn't doing anything with e-mail. :)
443 	if (be_roster->IsRunning(kSpamServerSignature)) {
444 		team_id serverTeam = be_roster->TeamFor(kSpamServerSignature);
445 		if (serverTeam >= 0) {
446 			int32 errorCode = B_SERVER_NOT_FOUND;
447 			BMessenger messengerToSpamServer(kSpamServerSignature, serverTeam, &errorCode);
448 			if (messengerToSpamServer.IsValid()) {
449 				BMessage quitMessage(B_QUIT_REQUESTED);
450 				messengerToSpamServer.SendMessage(&quitMessage);
451 			}
452 		}
453 
454 	}
455 
456 	SaveSettings();
457 	return true;
458 }
459 
460 
461 void
462 TMailApp::ReadyToRun()
463 {
464 	// Create needed indices for META:group, META:email, MAIL:draft,
465 	// INDEX_SIGNATURE, INDEX_STATUS on the boot volume
466 
467 	BVolume volume;
468 	BVolumeRoster().GetBootVolume(&volume);
469 
470 	fs_create_index(volume.Device(), "META:group", B_STRING_TYPE, 0);
471 	fs_create_index(volume.Device(), "META:email", B_STRING_TYPE, 0);
472 	fs_create_index(volume.Device(), "MAIL:draft", B_INT32_TYPE, 0);
473 	fs_create_index(volume.Device(), INDEX_SIGNATURE, B_STRING_TYPE, 0);
474 	fs_create_index(volume.Device(), INDEX_STATUS, B_STRING_TYPE, 0);
475 
476 	// Load dictionaries
477 	BPath indexDir;
478 	BPath dictionaryDir;
479 	BPath dataPath;
480 	BPath indexPath;
481 	BDirectory directory;
482 	BEntry entry;
483 
484 	// Locate user settings directory
485 	find_directory(B_SYSTEM_DATA_DIRECTORY, &indexDir, true);
486 	indexDir.Append("spell_check");
487 	dictionaryDir = indexDir;
488 
489 	// Setup directory paths
490 	indexDir.Append(kIndexDirectory);
491 	dictionaryDir.Append(kDictDirectory);
492 
493 	// Create directories if needed
494 	directory.CreateDirectory(indexDir.Path(), NULL);
495 	directory.CreateDirectory(dictionaryDir.Path(), NULL);
496 
497 	dataPath = dictionaryDir;
498 	dataPath.Append("words");
499 
500 	// Only Load if Words Dictionary
501 	if (BEntry(kWordsPath).Exists() || BEntry(dataPath.Path()).Exists()) {
502 		// If "/boot/optional/goodies/words" exists but there is no
503 		// system dictionary, copy words
504 		if (!BEntry(dataPath.Path()).Exists() && BEntry(kWordsPath).Exists()) {
505 			BFile words(kWordsPath, B_READ_ONLY);
506 			BFile copy(dataPath.Path(), B_WRITE_ONLY | B_CREATE_FILE);
507 			char buffer[4096];
508 			ssize_t size;
509 
510 			while ((size = words.Read( buffer, 4096)) > 0)
511 				copy.Write(buffer, size);
512 			BNodeInfo(&copy).SetType("text/plain");
513 		}
514 
515 		// Create user dictionary if it does not exist
516 		dataPath = dictionaryDir;
517 		dataPath.Append("user");
518 		if (!BEntry(dataPath.Path()).Exists()) {
519 			BFile user(dataPath.Path(), B_WRITE_ONLY | B_CREATE_FILE);
520 			BNodeInfo(&user).SetType("text/plain");
521 		}
522 
523 		// Load dictionaries
524 		directory.SetTo(dictionaryDir.Path());
525 
526 		BString leafName;
527 		gUserDict = -1;
528 
529 		while (gDictCount < MAX_DICTIONARIES
530 			&& directory.GetNextEntry(&entry) != B_ENTRY_NOT_FOUND) {
531 			dataPath.SetTo(&entry);
532 
533 			// Identify the user dictionary
534 			if (strcmp("user", dataPath.Leaf()) == 0) {
535 				gUserDictFile = new BFile(dataPath.Path(), B_WRITE_ONLY | B_OPEN_AT_END);
536 				gUserDict = gDictCount;
537 			}
538 
539 			indexPath = indexDir;
540 			leafName.SetTo(dataPath.Leaf());
541 			leafName.Append(kMetaphone);
542 			indexPath.Append(leafName.String());
543 			gWords[gDictCount] = new Words(dataPath.Path(), indexPath.Path(), true);
544 
545 			indexPath = indexDir;
546 			leafName.SetTo(dataPath.Leaf());
547 			leafName.Append(kExact);
548 			indexPath.Append(leafName.String());
549 			gExactWords[gDictCount] = new Words(dataPath.Path(), indexPath.Path(), false);
550 			gDictCount++;
551 		}
552 	}
553 
554 	// Create a new window if starting up without any extra arguments.
555 
556 	if (!fPrintHelpAndExit && !fWindowCount) {
557 		TMailWindow	*window;
558 		window = NewWindow();
559 		window->Show();
560 	}
561 }
562 
563 
564 void
565 TMailApp::RefsReceived(BMessage *msg)
566 {
567 	bool		have_names = false;
568 	BString		names;
569 	char		type[B_FILE_NAME_LENGTH];
570 	int32		item = 0;
571 	BFile		file;
572 	TMailWindow	*window;
573 	entry_ref	ref;
574 
575 	//
576 	// If a tracker window opened me, get a messenger from it.
577 	//
578 	BMessenger messenger;
579 	if (msg->HasMessenger("TrackerViewToken"))
580 		msg->FindMessenger("TrackerViewToken", &messenger);
581 
582 	while (msg->HasRef("refs", item)) {
583 		msg->FindRef("refs", item++, &ref);
584 		if ((window = FindWindow(ref)) != NULL)
585 			window->Activate(true);
586 		else {
587 			file.SetTo(&ref, O_RDONLY);
588 			if (file.InitCheck() == B_NO_ERROR) {
589 				BNodeInfo	node(&file);
590 				node.GetType(type);
591 				if (!strcmp(type, B_MAIL_TYPE)) {
592 					window = NewWindow(&ref, NULL, false, &messenger);
593 					window->Show();
594 				} else if(!strcmp(type, "application/x-person")) {
595 					/* Got a People contact info file, see if it has an Email address. */
596 					BString name;
597 					BString email;
598 					attr_info	info;
599 					char *attrib;
600 
601 					if (file.GetAttrInfo("META:email", &info) == B_NO_ERROR) {
602 						attrib = (char *) malloc(info.size + 1);
603 						file.ReadAttr("META:email", B_STRING_TYPE, 0, attrib, info.size);
604 						attrib[info.size] = 0; // Just in case it wasn't NUL terminated.
605 						email << attrib;
606 						free(attrib);
607 
608 						/* we got something... */
609 						if (email.Length() > 0) {
610 							/* see if we can get a username as well */
611 							if(file.GetAttrInfo("META:name", &info) == B_NO_ERROR) {
612 								attrib = (char *) malloc(info.size + 1);
613 								file.ReadAttr("META:name", B_STRING_TYPE, 0, attrib, info.size);
614 								attrib[info.size] = 0; // Just in case it wasn't NUL terminated.
615 								name << "\"" << attrib << "\" ";
616 								email.Prepend("<");
617 								email.Append(">");
618 								free(attrib);
619 							}
620 
621 							if (names.Length() == 0) {
622 								names << name << email;
623 							} else {
624 								names << ", " << name << email;
625 							}
626 							have_names = true;
627 							email.SetTo("");
628 							name.SetTo("");
629 						}
630 					}
631 				}
632 				else if (!strcmp(type, kDraftType))
633 				{
634 					window = NewWindow();
635 
636 					// If it's a draft message, open it
637 					window->OpenMessage(&ref);
638 					window->Show();
639 				}
640 			} /* end of else(file.InitCheck() == B_NO_ERROR */
641 		}
642 	}
643 
644 	if (have_names) {
645 		window = NewWindow(NULL, names.String());
646 		window->Show();
647 	}
648 }
649 
650 
651 TMailWindow *
652 TMailApp::FindWindow(const entry_ref &ref)
653 {
654 	BEntry entry(&ref);
655 	if (entry.InitCheck() < B_OK)
656 		return NULL;
657 
658 	node_ref nodeRef;
659 	if (entry.GetNodeRef(&nodeRef) < B_OK)
660 		return NULL;
661 
662 	BWindow	*window;
663 	int32 index = 0;
664 	while ((window = WindowAt(index++)) != NULL) {
665 		TMailWindow *mailWindow = dynamic_cast<TMailWindow *>(window);
666 		if (mailWindow == NULL)
667 			continue;
668 
669 		node_ref mailNodeRef;
670 		if (mailWindow->GetMailNodeRef(mailNodeRef) == B_OK
671 			&& mailNodeRef == nodeRef)
672 			return mailWindow;
673 	}
674 
675 	return NULL;
676 }
677 
678 
679 void
680 TMailApp::_CheckForSpamFilterExistence()
681 {
682 	// Looks at the filter settings to see if the user is using a spam filter.
683 	// If there is one there, set fShowSpamGUI to TRUE, otherwise to FALSE.
684 
685 	int32 addonNameIndex;
686 	const char *addonNamePntr;
687 	BDirectory inChainDir;
688 	BPath path;
689 	BEntry settingsEntry;
690 	BFile settingsFile;
691 	BMessage settingsMessage;
692 
693 	fShowSpamGUI = false;
694 
695 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
696 		return;
697 	path.Append("Mail/chains/inbound");
698 	if (inChainDir.SetTo(path.Path()) != B_OK)
699 		return;
700 
701 	while (inChainDir.GetNextEntry (&settingsEntry, true /* traverse */) == B_OK) {
702 		if (!settingsEntry.IsFile())
703 			continue;
704 		if (settingsFile.SetTo (&settingsEntry, B_READ_ONLY) != B_OK)
705 			continue;
706 		if (settingsMessage.Unflatten (&settingsFile) != B_OK)
707 			continue;
708 		for (addonNameIndex = 0; B_OK == settingsMessage.FindString (
709 			"filter_addons", addonNameIndex, &addonNamePntr);
710 			addonNameIndex++) {
711 			if (strstr (addonNamePntr, "Spam Filter") != NULL) {
712 				fShowSpamGUI = true; // Found it!
713 				return;
714 			}
715 		}
716 	}
717 }
718 
719 
720 void
721 TMailApp::SetPrintSettings(const BMessage* printSettings)
722 {
723 	BAutolock _(this);
724 
725 	if (printSettings == fPrintSettings)
726 		return;
727 
728 	delete fPrintSettings;
729 	if (printSettings)
730 		fPrintSettings = new BMessage(*printSettings);
731 	else
732 		fPrintSettings = NULL;
733 }
734 
735 
736 bool
737 TMailApp::HasPrintSettings()
738 {
739 	BAutolock _(this);
740 	return fPrintSettings != NULL;
741 }
742 
743 
744 BMessage
745 TMailApp::PrintSettings()
746 {
747 	BAutolock _(this);
748 	return BMessage(*fPrintSettings);
749 }
750 
751 
752 void
753 TMailApp::_ClearPrintSettings()
754 {
755 	delete fPrintSettings;
756 	fPrintSettings = NULL;
757 }
758 
759 
760 void
761 TMailApp::SetLastWindowFrame(BRect frame)
762 {
763 	BAutolock _(this);
764 	fLastMailWindowFrame = frame;
765 }
766 
767 
768 status_t
769 TMailApp::GetSettingsPath(BPath &path)
770 {
771 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
772 	if (status != B_OK)
773 		return status;
774 
775 	path.Append("Mail");
776 	return create_directory(path.Path(), 0755);
777 }
778 
779 
780 status_t
781 TMailApp::LoadOldSettings()
782 {
783 	BPath path;
784 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
785 	if (status != B_OK)
786 		return status;
787 
788 	path.Append("Mail_data");
789 
790 	BFile file;
791 	status = file.SetTo(path.Path(), B_READ_ONLY);
792 	if (status != B_OK)
793 		return status;
794 
795 	file.Read(&fMailWindowFrame, sizeof(BRect));
796 //	file.Read(&level, sizeof(level));
797 
798 	font_family	fontFamily;
799 	font_style	fontStyle;
800 	float size;
801 	file.Read(&fontFamily, sizeof(font_family));
802 	file.Read(&fontStyle, sizeof(font_style));
803 	file.Read(&size, sizeof(float));
804 	if (size >= 9)
805 		fContentFont.SetSize(size);
806 
807 	if (fontFamily[0] && fontStyle[0])
808 		fContentFont.SetFamilyAndStyle(fontFamily, fontStyle);
809 
810 	file.Read(&fSignatureWindowFrame, sizeof(BRect));
811 	file.Seek(1, SEEK_CUR);	// ignore (bool) show header
812 	file.Read(&fWrapMode, sizeof(bool));
813 	file.Read(&fPrefsWindowPos, sizeof(BPoint));
814 
815 	int32 length;
816 	if (file.Read(&length, sizeof(int32)) < (ssize_t)sizeof(int32))
817 		return B_IO_ERROR;
818 
819 	free(fSignature);
820 	fSignature = NULL;
821 
822 	if (length > 0) {
823 		fSignature = (char *)malloc(length);
824 		if (fSignature == NULL)
825 			return B_NO_MEMORY;
826 
827 		file.Read(fSignature, length);
828 	}
829 
830 	file.Read(&fMailCharacterSet, sizeof(int32));
831 	if (fMailCharacterSet != B_MAIL_UTF8_CONVERSION
832 		&& fMailCharacterSet != B_MAIL_US_ASCII_CONVERSION
833 		&& BCharacterSetRoster::GetCharacterSetByConversionID(fMailCharacterSet) == NULL)
834 		fMailCharacterSet = B_MS_WINDOWS_CONVERSION;
835 
836 	if (file.Read(&length, sizeof(int32)) == (ssize_t)sizeof(int32)) {
837 		char *findString = (char *)malloc(length + 1);
838 		if (findString == NULL)
839 			return B_NO_MEMORY;
840 
841 		file.Read(findString, length);
842 		findString[length] = '\0';
843 		FindWindow::SetFindString(findString);
844 		free(findString);
845 	}
846 	if (file.Read(&fShowButtonBar, sizeof(uint8)) < (ssize_t)sizeof(uint8))
847 		fShowButtonBar = true;
848 	if (file.Read(&fUseAccountFrom, sizeof(int32)) < (ssize_t)sizeof(int32)
849 		|| fUseAccountFrom < ACCOUNT_USE_DEFAULT
850 		|| fUseAccountFrom > ACCOUNT_FROM_MAIL)
851 		fUseAccountFrom = ACCOUNT_USE_DEFAULT;
852 	if (file.Read(&fColoredQuotes, sizeof(bool)) < (ssize_t)sizeof(bool))
853 		fColoredQuotes = true;
854 
855 	if (file.Read(&length, sizeof(int32)) == (ssize_t)sizeof(int32)) {
856 		free(fReplyPreamble);
857 		fReplyPreamble = (char *)malloc(length + 1);
858 		if (fReplyPreamble == NULL)
859 			return B_NO_MEMORY;
860 
861 		file.Read(fReplyPreamble, length);
862 		fReplyPreamble[length] = '\0';
863 	}
864 
865 	file.Read(&fAttachAttributes, sizeof(bool));
866 	file.Read(&fWarnAboutUnencodableCharacters, sizeof(bool));
867 
868 	return B_OK;
869 }
870 
871 
872 status_t
873 TMailApp::SaveSettings()
874 {
875 	BMailSettings chainSettings;
876 
877 	if (fDefaultChain != ~0UL) {
878 		chainSettings.SetDefaultOutboundChainID(fDefaultChain);
879 		chainSettings.Save();
880 	}
881 
882 	BPath path;
883 	status_t status = GetSettingsPath(path);
884 	if (status != B_OK)
885 		return status;
886 
887 	path.Append("BeMail Settings~");
888 
889 	BFile file;
890 	status = file.SetTo(path.Path(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
891 	if (status != B_OK)
892 		return status;
893 
894 	BMessage settings('BeMl');
895 	settings.AddRect("MailWindowSize", fMailWindowFrame);
896 //	settings.AddInt32("ExperienceLevel", level);
897 
898 	font_family fontFamily;
899 	font_style fontStyle;
900 	fContentFont.GetFamilyAndStyle(&fontFamily, &fontStyle);
901 
902 	settings.AddString("FontFamily", fontFamily);
903 	settings.AddString("FontStyle", fontStyle);
904 	settings.AddFloat("FontSize", fContentFont.Size());
905 
906 	settings.AddRect("SignatureWindowSize", fSignatureWindowFrame);
907 	settings.AddBool("WordWrapMode", fWrapMode);
908 	settings.AddPoint("PreferencesWindowLocation", fPrefsWindowPos);
909 	settings.AddBool("AutoMarkRead", fAutoMarkRead);
910 	settings.AddString("SignatureText", fSignature);
911 	settings.AddInt32("CharacterSet", fMailCharacterSet);
912 	settings.AddString("FindString", FindWindow::GetFindString());
913 	settings.AddInt8("ShowButtonBar", fShowButtonBar);
914 	settings.AddInt32("UseAccountFrom", fUseAccountFrom);
915 	settings.AddBool("ColoredQuotes", fColoredQuotes);
916 	settings.AddString("ReplyPreamble", fReplyPreamble);
917 	settings.AddBool("AttachAttributes", fAttachAttributes);
918 	settings.AddBool("WarnAboutUnencodableCharacters", fWarnAboutUnencodableCharacters);
919 	settings.AddBool("StartWithSpellCheck", fStartWithSpellCheckOn);
920 
921 	BEntry entry;
922 	status = entry.SetTo(path.Path());
923 	if (status != B_OK)
924 		return status;
925 
926 	status = settings.Flatten(&file);
927 	if (status == B_OK) {
928 		// replace original settings file
929 		status = entry.Rename("BeMail Settings", true);
930 	} else
931 		entry.Remove();
932 
933 	return status;
934 }
935 
936 
937 status_t
938 TMailApp::LoadSettings()
939 {
940 	BMailSettings chainSettings;
941 	fDefaultChain = chainSettings.DefaultOutboundChainID();
942 
943 	BPath path;
944 	status_t status = GetSettingsPath(path);
945 	if (status != B_OK)
946 		return status;
947 
948 	path.Append("BeMail Settings");
949 
950 	BFile file;
951 	status = file.SetTo(path.Path(), B_READ_ONLY);
952 	if (status != B_OK)
953 		return LoadOldSettings();
954 
955 	BMessage settings;
956 	status = settings.Unflatten(&file);
957 	if (status < B_OK || settings.what != 'BeMl') {
958 		// the current settings are corrupted, try old ones
959 		return LoadOldSettings();
960 	}
961 
962 	BRect rect;
963 	if (settings.FindRect("MailWindowSize", &rect) == B_OK)
964 		fMailWindowFrame = rect;
965 
966 	int32 int32Value;
967 //	if (settings.FindInt32("ExperienceLevel", &int32Value) == B_OK)
968 //		level = int32Value;
969 
970 	const char *fontFamily;
971 	if (settings.FindString("FontFamily", &fontFamily) == B_OK) {
972 		const char *fontStyle;
973 		if (settings.FindString("FontStyle", &fontStyle) == B_OK) {
974 			float size;
975 			if (settings.FindFloat("FontSize", &size) == B_OK) {
976 				if (size >= 7)
977 					fContentFont.SetSize(size);
978 
979 				if (fontFamily[0] && fontStyle[0]) {
980 					fContentFont.SetFamilyAndStyle(fontFamily[0] ? fontFamily : NULL,
981 						fontStyle[0] ? fontStyle : NULL);
982 				}
983 			}
984 		}
985 	}
986 
987 	if (settings.FindRect("SignatureWindowSize", &rect) == B_OK)
988 		fSignatureWindowFrame = rect;
989 
990 	bool boolValue;
991 	if (settings.FindBool("WordWrapMode", &boolValue) == B_OK)
992 		fWrapMode = boolValue;
993 
994 	BPoint point;
995 	if (settings.FindPoint("PreferencesWindowLocation", &point) == B_OK)
996 		fPrefsWindowPos = point;
997 
998 	if (settings.FindBool("AutoMarkRead", &boolValue) == B_OK)
999 		fAutoMarkRead = boolValue;
1000 
1001 	const char *string;
1002 	if (settings.FindString("SignatureText", &string) == B_OK) {
1003 		free(fSignature);
1004 		fSignature = strdup(string);
1005 	}
1006 
1007 	if (settings.FindInt32("CharacterSet", &int32Value) == B_OK)
1008 		fMailCharacterSet = int32Value;
1009 	if (fMailCharacterSet != B_MAIL_UTF8_CONVERSION
1010 		&& fMailCharacterSet != B_MAIL_US_ASCII_CONVERSION
1011 		&& BCharacterSetRoster::GetCharacterSetByConversionID(fMailCharacterSet) == NULL)
1012 		fMailCharacterSet = B_MS_WINDOWS_CONVERSION;
1013 
1014 	if (settings.FindString("FindString", &string) == B_OK)
1015 		FindWindow::SetFindString(string);
1016 
1017 	int8 int8Value;
1018 	if (settings.FindInt8("ShowButtonBar", &int8Value) == B_OK)
1019 		fShowButtonBar = int8Value;
1020 
1021 	if (settings.FindInt32("UseAccountFrom", &int32Value) == B_OK)
1022 		fUseAccountFrom = int32Value;
1023 	if (fUseAccountFrom < ACCOUNT_USE_DEFAULT
1024 		|| fUseAccountFrom > ACCOUNT_FROM_MAIL)
1025 		fUseAccountFrom = ACCOUNT_USE_DEFAULT;
1026 
1027 	if (settings.FindBool("ColoredQuotes", &boolValue) == B_OK)
1028 		fColoredQuotes = boolValue;
1029 
1030 	if (settings.FindString("ReplyPreamble", &string) == B_OK) {
1031 		free(fReplyPreamble);
1032 		fReplyPreamble = strdup(string);
1033 	}
1034 
1035 	if (settings.FindBool("AttachAttributes", &boolValue) == B_OK)
1036 		fAttachAttributes = boolValue;
1037 
1038 	if (settings.FindBool("WarnAboutUnencodableCharacters", &boolValue) == B_OK)
1039 		fWarnAboutUnencodableCharacters = boolValue;
1040 
1041 	if (settings.FindBool("StartWithSpellCheck", &boolValue) == B_OK)
1042 		fStartWithSpellCheckOn = boolValue;
1043 
1044 	return B_OK;
1045 }
1046 
1047 
1048 void
1049 TMailApp::FontChange()
1050 {
1051 	int32		index = 0;
1052 	BMessage	msg;
1053 	BWindow		*window;
1054 
1055 	msg.what = CHANGE_FONT;
1056 	msg.AddPointer("font", &fContentFont);
1057 
1058 	for (;;) {
1059 		window = WindowAt(index++);
1060 		if (!window)
1061 			break;
1062 
1063 		window->PostMessage(&msg);
1064 	}
1065 }
1066 
1067 
1068 TMailWindow*
1069 TMailApp::NewWindow(const entry_ref* ref, const char* to, bool resend,
1070 	BMessenger* trackerMessenger)
1071 {
1072 	BScreen screen(B_MAIN_SCREEN_ID);
1073 	BRect screenFrame = screen.Frame();
1074 
1075 	BRect r;
1076 	if (fMailWindowFrame.Width() < 64 || fMailWindowFrame.Height() < 20) {
1077 		// default size
1078 		r.Set(6, TITLE_BAR_HEIGHT, 6 + WIND_WIDTH,
1079 			TITLE_BAR_HEIGHT + WIND_HEIGHT);
1080 	} else
1081 		r = fMailWindowFrame;
1082 
1083 	// make sure the window is not larger than the screen space
1084 	if (r.Height() > screenFrame.Height())
1085 		r.bottom = r.top + screenFrame.Height();
1086 	if (r.Width() > screenFrame.Width())
1087 		r.bottom = r.top + screenFrame.Width();
1088 
1089 	// cascading windows
1090 	r.OffsetBy(((fWindowCount + 5) % 10) * 15 - 75,
1091 		((fWindowCount + 5) % 10) * 15 - 75);
1092 
1093 	// make sure the window is still on screen
1094 	if (r.left - 6 < screenFrame.left)
1095 		r.OffsetTo(screenFrame.left + 8, r.top);
1096 
1097 	if (r.left + 20 > screenFrame.right)
1098 		r.OffsetTo(6, r.top);
1099 
1100 	if (r.top - 26 < screenFrame.top)
1101 		r.OffsetTo(r.left, screenFrame.top + 26);
1102 
1103 	if (r.top + 20 > screenFrame.bottom)
1104 		r.OffsetTo(r.left, TITLE_BAR_HEIGHT);
1105 
1106 	if (r.Width() < WIND_WIDTH)
1107 		r.right = r.left + WIND_WIDTH;
1108 
1109 	fWindowCount++;
1110 
1111 	BString title;
1112 	BFile file;
1113 	if (!resend && ref && file.SetTo(ref, O_RDONLY) == B_OK) {
1114 		BString name;
1115 		if (ReadAttrString(&file, B_MAIL_ATTR_NAME, &name) == B_OK) {
1116 			title << name;
1117 			BString subject;
1118 			if (ReadAttrString(&file, B_MAIL_ATTR_SUBJECT, &subject) == B_OK)
1119 				title << " -> " << subject;
1120 		}
1121 	}
1122 	if (title == "")
1123 		title = "Mail";
1124 
1125 	TMailWindow* window = new TMailWindow(r, title.String(), this, ref, to,
1126 		&fContentFont, resend, trackerMessenger);
1127 	fWindowList.AddItem(window);
1128 
1129 	return window;
1130 }
1131 
1132 
1133 // #pragma mark - settings
1134 
1135 
1136 bool
1137 TMailApp::AutoMarkRead()
1138 {
1139 	BAutolock _(this);
1140 	return fAutoMarkRead;
1141 }
1142 
1143 
1144 BString
1145 TMailApp::Signature()
1146 {
1147 	BAutolock _(this);
1148 	return BString(fSignature);
1149 }
1150 
1151 
1152 BString
1153 TMailApp::ReplyPreamble()
1154 {
1155 	BAutolock _(this);
1156 	return BString(fReplyPreamble);
1157 }
1158 
1159 
1160 bool
1161 TMailApp::WrapMode()
1162 {
1163 	BAutolock _(this);
1164 	return fWrapMode;
1165 }
1166 
1167 
1168 bool
1169 TMailApp::AttachAttributes()
1170 {
1171 	BAutolock _(this);
1172 	return fAttachAttributes;
1173 }
1174 
1175 
1176 bool
1177 TMailApp::ColoredQuotes()
1178 {
1179 	BAutolock _(this);
1180 	return fColoredQuotes;
1181 }
1182 
1183 
1184 uint8
1185 TMailApp::ShowButtonBar()
1186 {
1187 	BAutolock _(this);
1188 	return fShowButtonBar;
1189 }
1190 
1191 
1192 bool
1193 TMailApp::WarnAboutUnencodableCharacters()
1194 {
1195 	BAutolock _(this);
1196 	return fWarnAboutUnencodableCharacters;
1197 }
1198 
1199 
1200 bool
1201 TMailApp::StartWithSpellCheckOn()
1202 {
1203 	BAutolock _(this);
1204 	return fStartWithSpellCheckOn;
1205 }
1206 
1207 
1208 void
1209 TMailApp::SetDefaultChain(uint32 chain)
1210 {
1211 	BAutolock _(this);
1212 	fDefaultChain = chain;
1213 }
1214 
1215 
1216 uint32
1217 TMailApp::DefaultChain()
1218 {
1219 	BAutolock _(this);
1220 	return fDefaultChain;
1221 }
1222 
1223 
1224 int32
1225 TMailApp::UseAccountFrom()
1226 {
1227 	BAutolock _(this);
1228 	return fUseAccountFrom;
1229 }
1230 
1231 
1232 uint32
1233 TMailApp::MailCharacterSet()
1234 {
1235 	BAutolock _(this);
1236 	return fMailCharacterSet;
1237 }
1238 
1239 
1240 BFont
1241 TMailApp::ContentFont()
1242 {
1243 	BAutolock _(this);
1244 	return fContentFont;
1245 }
1246 
1247 
1248 //	#pragma mark -
1249 
1250 
1251 int
1252 main()
1253 {
1254 	tzset();
1255 
1256 	TMailApp().Run();
1257 	return B_OK;
1258 }
1259 
1260