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