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