xref: /haiku/src/apps/mail/MailApp.cpp (revision 546208a53940a26c6379c48a7854ade1a8250fc5)
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 <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 
72 using 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 
95 static const char *kDictDirectory = "word_dictionary";
96 static const char *kIndexDirectory = "word_index";
97 static const char *kWordsPath = "/boot/optional/goodies/words";
98 static const char *kExact = ".exact";
99 static const char *kMetaphone = ".metaphone";
100 
101 
102 TMailApp::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 
154 TMailApp::~TMailApp()
155 {
156 }
157 
158 
159 void
160 TMailApp::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 
249 void
250 TMailApp::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 
419 bool
420 TMailApp::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 
449 void
450 TMailApp::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 
464 	// Load dictionaries
465 	BPath indexDir;
466 	BPath dictionaryDir;
467 	BPath userDictionaryDir;
468 	BPath userIndexDir;
469 	BPath dataPath;
470 	BPath indexPath;
471 	BDirectory directory;
472 	BEntry entry;
473 
474 	// Locate dictionaries directory
475 	find_directory(B_SYSTEM_DATA_DIRECTORY, &indexDir, true);
476 	indexDir.Append("spell_check");
477 	dictionaryDir = indexDir;
478 
479 	//Locate user dictionary directory
480 	find_directory(B_USER_CONFIG_DIRECTORY, &userIndexDir, true);
481 	userIndexDir.Append("data/spell_check");
482 	userDictionaryDir = userIndexDir;
483 
484 	// Create directory if needed
485 	directory.CreateDirectory(userIndexDir.Path(),  NULL);
486 
487 	// Setup directory paths
488 	indexDir.Append(kIndexDirectory);
489 	dictionaryDir.Append(kDictDirectory);
490 	userIndexDir.Append(kIndexDirectory);
491 	userDictionaryDir.Append(kDictDirectory);
492 
493 	// Create directories if needed
494 	directory.CreateDirectory(indexDir.Path(), NULL);
495 	directory.CreateDirectory(dictionaryDir.Path(), NULL);
496 	directory.CreateDirectory(userIndexDir.Path(), NULL);
497 	directory.CreateDirectory(userDictionaryDir.Path(), NULL);
498 
499 	dataPath = dictionaryDir;
500 	dataPath.Append("words");
501 
502 	// Only Load if Words Dictionary
503 	if (BEntry(kWordsPath).Exists() || BEntry(dataPath.Path()).Exists()) {
504 		// If "/boot/optional/goodies/words" exists but there is no
505 		// system dictionary, copy words
506 		if (!BEntry(dataPath.Path()).Exists() && BEntry(kWordsPath).Exists()) {
507 			BFile words(kWordsPath, B_READ_ONLY);
508 			BFile copy(dataPath.Path(), B_WRITE_ONLY | B_CREATE_FILE);
509 			char buffer[4096];
510 			ssize_t size;
511 
512 			while ((size = words.Read( buffer, 4096)) > 0)
513 				copy.Write(buffer, size);
514 			BNodeInfo(&copy).SetType("text/plain");
515 		}
516 
517 		// Load dictionaries
518 		directory.SetTo(dictionaryDir.Path());
519 
520 		BString leafName;
521 		gUserDict = -1;
522 
523 		while (gDictCount < MAX_DICTIONARIES
524 			&& directory.GetNextEntry(&entry) != B_ENTRY_NOT_FOUND) {
525 			dataPath.SetTo(&entry);
526 
527 			indexPath = indexDir;
528 			leafName.SetTo(dataPath.Leaf());
529 			leafName.Append(kMetaphone);
530 			indexPath.Append(leafName.String());
531 			gWords[gDictCount] = new Words(dataPath.Path(), indexPath.Path(), true);
532 
533 			indexPath = indexDir;
534 			leafName.SetTo(dataPath.Leaf());
535 			leafName.Append(kExact);
536 			indexPath.Append(leafName.String());
537 			gExactWords[gDictCount] = new Words(dataPath.Path(), indexPath.Path(), false);
538 			gDictCount++;
539 		}
540 
541 		// Create user dictionary if it does not exist
542 		dataPath = userDictionaryDir;
543 		dataPath.Append("user");
544 		if (!BEntry(dataPath.Path()).Exists()) {
545 			BFile user(dataPath.Path(), B_WRITE_ONLY | B_CREATE_FILE);
546 			BNodeInfo(&user).SetType("text/plain");
547 		}
548 
549 		// Load user dictionary
550 		if (BEntry(userDictionaryDir.Path()).Exists()) {
551 			gUserDictFile = new BFile(dataPath.Path(), B_WRITE_ONLY | B_OPEN_AT_END);
552 			gUserDict = gDictCount;
553 
554 			indexPath = userIndexDir;
555 			leafName.SetTo(dataPath.Leaf());
556 			leafName.Append(kMetaphone);
557 			indexPath.Append(leafName.String());
558 			gWords[gDictCount] = new Words(dataPath.Path(), indexPath.Path(), true);
559 
560 			indexPath = userIndexDir;
561 			leafName.SetTo(dataPath.Leaf());
562 			leafName.Append(kExact);
563 			indexPath.Append(leafName.String());
564 			gExactWords[gDictCount] = new Words(dataPath.Path(), indexPath.Path(), false);
565 			gDictCount++;
566 		}
567 	}
568 
569 	// Create a new window if starting up without any extra arguments.
570 
571 	if (!fPrintHelpAndExit && !fWindowCount) {
572 		TMailWindow	*window;
573 		window = NewWindow();
574 		window->Show();
575 	}
576 }
577 
578 
579 void
580 TMailApp::RefsReceived(BMessage *msg)
581 {
582 	bool		have_names = false;
583 	BString		names;
584 	char		type[B_FILE_NAME_LENGTH];
585 	int32		item = 0;
586 	BFile		file;
587 	TMailWindow	*window;
588 	entry_ref	ref;
589 
590 	//
591 	// If a tracker window opened me, get a messenger from it.
592 	//
593 	BMessenger messenger;
594 	if (msg->HasMessenger("TrackerViewToken"))
595 		msg->FindMessenger("TrackerViewToken", &messenger);
596 
597 	while (msg->HasRef("refs", item)) {
598 		msg->FindRef("refs", item++, &ref);
599 		if ((window = FindWindow(ref)) != NULL)
600 			window->Activate(true);
601 		else {
602 			file.SetTo(&ref, O_RDONLY);
603 			if (file.InitCheck() == B_NO_ERROR) {
604 				BNodeInfo	node(&file);
605 				node.GetType(type);
606 				if (strcmp(type, B_MAIL_TYPE) == 0
607 					|| strcmp(type, B_PARTIAL_MAIL_TYPE) == 0) {
608 					window = NewWindow(&ref, NULL, false, &messenger);
609 					window->Show();
610 				} else if(strcmp(type, "application/x-person") == 0) {
611 					/* Got a People contact info file, see if it has an Email address. */
612 					BString name;
613 					BString email;
614 					attr_info	info;
615 					char *attrib;
616 
617 					if (file.GetAttrInfo("META:email", &info) == B_NO_ERROR) {
618 						attrib = (char *) malloc(info.size + 1);
619 						file.ReadAttr("META:email", B_STRING_TYPE, 0, attrib, info.size);
620 						attrib[info.size] = 0; // Just in case it wasn't NUL terminated.
621 						email << attrib;
622 						free(attrib);
623 
624 						/* we got something... */
625 						if (email.Length() > 0) {
626 							/* see if we can get a username as well */
627 							if(file.GetAttrInfo("META:name", &info) == B_NO_ERROR) {
628 								attrib = (char *) malloc(info.size + 1);
629 								file.ReadAttr("META:name", B_STRING_TYPE, 0, attrib, info.size);
630 								attrib[info.size] = 0; // Just in case it wasn't NUL terminated.
631 								name << "\"" << attrib << "\" ";
632 								email.Prepend("<");
633 								email.Append(">");
634 								free(attrib);
635 							}
636 
637 							if (names.Length() == 0) {
638 								names << name << email;
639 							} else {
640 								names << ", " << name << email;
641 							}
642 							have_names = true;
643 							email.SetTo("");
644 							name.SetTo("");
645 						}
646 					}
647 				}
648 				else if (strcmp(type, kDraftType) == 0) {
649 					window = NewWindow();
650 
651 					// If it's a draft message, open it
652 					window->OpenMessage(&ref);
653 					window->Show();
654 				}
655 			} /* end of else(file.InitCheck() == B_NO_ERROR */
656 		}
657 	}
658 
659 	if (have_names) {
660 		window = NewWindow(NULL, names.String());
661 		window->Show();
662 	}
663 }
664 
665 
666 TMailWindow *
667 TMailApp::FindWindow(const entry_ref &ref)
668 {
669 	BEntry entry(&ref);
670 	if (entry.InitCheck() < B_OK)
671 		return NULL;
672 
673 	node_ref nodeRef;
674 	if (entry.GetNodeRef(&nodeRef) < B_OK)
675 		return NULL;
676 
677 	BWindow	*window;
678 	int32 index = 0;
679 	while ((window = WindowAt(index++)) != NULL) {
680 		TMailWindow *mailWindow = dynamic_cast<TMailWindow *>(window);
681 		if (mailWindow == NULL)
682 			continue;
683 
684 		node_ref mailNodeRef;
685 		if (mailWindow->GetMailNodeRef(mailNodeRef) == B_OK
686 			&& mailNodeRef == nodeRef)
687 			return mailWindow;
688 	}
689 
690 	return NULL;
691 }
692 
693 
694 void
695 TMailApp::_CheckForSpamFilterExistence()
696 {
697 	// Looks at the filter settings to see if the user is using a spam filter.
698 	// If there is one there, set fShowSpamGUI to TRUE, otherwise to FALSE.
699 
700 	int32 addonNameIndex;
701 	const char *addonNamePntr;
702 	BDirectory inChainDir;
703 	BPath path;
704 	BEntry settingsEntry;
705 	BFile settingsFile;
706 	BMessage settingsMessage;
707 
708 	fShowSpamGUI = false;
709 
710 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
711 		return;
712 	// TODO use new settings
713 	path.Append("Mail/chains/inbound");
714 	if (inChainDir.SetTo(path.Path()) != B_OK)
715 		return;
716 
717 	while (inChainDir.GetNextEntry (&settingsEntry, true /* traverse */) == B_OK) {
718 		if (!settingsEntry.IsFile())
719 			continue;
720 		if (settingsFile.SetTo (&settingsEntry, B_READ_ONLY) != B_OK)
721 			continue;
722 		if (settingsMessage.Unflatten (&settingsFile) != B_OK)
723 			continue;
724 		for (addonNameIndex = 0; B_OK == settingsMessage.FindString (
725 			"filter_addons", addonNameIndex, &addonNamePntr);
726 			addonNameIndex++) {
727 			if (strstr (addonNamePntr, "Spam Filter") != NULL) {
728 				fShowSpamGUI = true; // Found it!
729 				return;
730 			}
731 		}
732 	}
733 }
734 
735 
736 void
737 TMailApp::SetPrintSettings(const BMessage* printSettings)
738 {
739 	BAutolock _(this);
740 
741 	if (printSettings == fPrintSettings)
742 		return;
743 
744 	delete fPrintSettings;
745 	if (printSettings)
746 		fPrintSettings = new BMessage(*printSettings);
747 	else
748 		fPrintSettings = NULL;
749 }
750 
751 
752 bool
753 TMailApp::HasPrintSettings()
754 {
755 	BAutolock _(this);
756 	return fPrintSettings != NULL;
757 }
758 
759 
760 BMessage
761 TMailApp::PrintSettings()
762 {
763 	BAutolock _(this);
764 	return BMessage(*fPrintSettings);
765 }
766 
767 
768 void
769 TMailApp::_ClearPrintSettings()
770 {
771 	delete fPrintSettings;
772 	fPrintSettings = NULL;
773 }
774 
775 
776 void
777 TMailApp::SetLastWindowFrame(BRect frame)
778 {
779 	BAutolock _(this);
780 	fLastMailWindowFrame = frame;
781 }
782 
783 
784 status_t
785 TMailApp::GetSettingsPath(BPath &path)
786 {
787 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
788 	if (status != B_OK)
789 		return status;
790 
791 	path.Append("Mail");
792 	return create_directory(path.Path(), 0755);
793 }
794 
795 
796 status_t
797 TMailApp::LoadOldSettings()
798 {
799 	BPath path;
800 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
801 	if (status != B_OK)
802 		return status;
803 
804 	path.Append("Mail_data");
805 
806 	BFile file;
807 	status = file.SetTo(path.Path(), B_READ_ONLY);
808 	if (status != B_OK)
809 		return status;
810 
811 	file.Read(&fMailWindowFrame, sizeof(BRect));
812 //	file.Read(&level, sizeof(level));
813 
814 	font_family	fontFamily;
815 	font_style	fontStyle;
816 	float size;
817 	file.Read(&fontFamily, sizeof(font_family));
818 	file.Read(&fontStyle, sizeof(font_style));
819 	file.Read(&size, sizeof(float));
820 	if (size >= 9)
821 		fContentFont.SetSize(size);
822 
823 	if (fontFamily[0] && fontStyle[0])
824 		fContentFont.SetFamilyAndStyle(fontFamily, fontStyle);
825 
826 	file.Read(&fSignatureWindowFrame, sizeof(BRect));
827 	file.Seek(1, SEEK_CUR);	// ignore (bool) show header
828 	file.Read(&fWrapMode, sizeof(bool));
829 	file.Read(&fPrefsWindowPos, sizeof(BPoint));
830 
831 	int32 length;
832 	if (file.Read(&length, sizeof(int32)) < (ssize_t)sizeof(int32))
833 		return B_IO_ERROR;
834 
835 	free(fSignature);
836 	fSignature = NULL;
837 
838 	if (length > 0) {
839 		fSignature = (char *)malloc(length);
840 		if (fSignature == NULL)
841 			return B_NO_MEMORY;
842 
843 		file.Read(fSignature, length);
844 	}
845 
846 	file.Read(&fMailCharacterSet, sizeof(int32));
847 	if (fMailCharacterSet != B_MAIL_UTF8_CONVERSION
848 		&& fMailCharacterSet != B_MAIL_US_ASCII_CONVERSION
849 		&& BCharacterSetRoster::GetCharacterSetByConversionID(fMailCharacterSet) == NULL)
850 		fMailCharacterSet = B_MS_WINDOWS_CONVERSION;
851 
852 	if (file.Read(&length, sizeof(int32)) == (ssize_t)sizeof(int32)) {
853 		char *findString = (char *)malloc(length + 1);
854 		if (findString == NULL)
855 			return B_NO_MEMORY;
856 
857 		file.Read(findString, length);
858 		findString[length] = '\0';
859 		FindWindow::SetFindString(findString);
860 		free(findString);
861 	}
862 	if (file.Read(&fShowButtonBar, sizeof(uint8)) < (ssize_t)sizeof(uint8))
863 		fShowButtonBar = true;
864 	if (file.Read(&fUseAccountFrom, sizeof(int32)) < (ssize_t)sizeof(int32)
865 		|| fUseAccountFrom < ACCOUNT_USE_DEFAULT
866 		|| fUseAccountFrom > ACCOUNT_FROM_MAIL)
867 		fUseAccountFrom = ACCOUNT_USE_DEFAULT;
868 	if (file.Read(&fColoredQuotes, sizeof(bool)) < (ssize_t)sizeof(bool))
869 		fColoredQuotes = true;
870 
871 	if (file.Read(&length, sizeof(int32)) == (ssize_t)sizeof(int32)) {
872 		free(fReplyPreamble);
873 		fReplyPreamble = (char *)malloc(length + 1);
874 		if (fReplyPreamble == NULL)
875 			return B_NO_MEMORY;
876 
877 		file.Read(fReplyPreamble, length);
878 		fReplyPreamble[length] = '\0';
879 	}
880 
881 	file.Read(&fAttachAttributes, sizeof(bool));
882 	file.Read(&fWarnAboutUnencodableCharacters, sizeof(bool));
883 
884 	return B_OK;
885 }
886 
887 
888 status_t
889 TMailApp::SaveSettings()
890 {
891 	BMailSettings accountSettings;
892 
893 	if (fDefaultAccount != ~0L) {
894 		accountSettings.SetDefaultOutboundAccount(fDefaultAccount);
895 		accountSettings.Save();
896 	}
897 
898 	BPath path;
899 	status_t status = GetSettingsPath(path);
900 	if (status != B_OK)
901 		return status;
902 
903 	path.Append("BeMail Settings~");
904 
905 	BFile file;
906 	status = file.SetTo(path.Path(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
907 	if (status != B_OK)
908 		return status;
909 
910 	BMessage settings('BeMl');
911 	settings.AddRect("MailWindowSize", fMailWindowFrame);
912 //	settings.AddInt32("ExperienceLevel", level);
913 
914 	font_family fontFamily;
915 	font_style fontStyle;
916 	fContentFont.GetFamilyAndStyle(&fontFamily, &fontStyle);
917 
918 	settings.AddString("FontFamily", fontFamily);
919 	settings.AddString("FontStyle", fontStyle);
920 	settings.AddFloat("FontSize", fContentFont.Size());
921 
922 	settings.AddRect("SignatureWindowSize", fSignatureWindowFrame);
923 	settings.AddBool("WordWrapMode", fWrapMode);
924 	settings.AddPoint("PreferencesWindowLocation", fPrefsWindowPos);
925 	settings.AddBool("AutoMarkRead", fAutoMarkRead);
926 	settings.AddString("SignatureText", fSignature);
927 	settings.AddInt32("CharacterSet", fMailCharacterSet);
928 	settings.AddString("FindString", FindWindow::GetFindString());
929 	settings.AddInt8("ShowButtonBar", fShowButtonBar);
930 	settings.AddInt32("UseAccountFrom", fUseAccountFrom);
931 	settings.AddBool("ColoredQuotes", fColoredQuotes);
932 	settings.AddString("ReplyPreamble", fReplyPreamble);
933 	settings.AddBool("AttachAttributes", fAttachAttributes);
934 	settings.AddBool("WarnAboutUnencodableCharacters", fWarnAboutUnencodableCharacters);
935 	settings.AddBool("StartWithSpellCheck", fStartWithSpellCheckOn);
936 
937 	BEntry entry;
938 	status = entry.SetTo(path.Path());
939 	if (status != B_OK)
940 		return status;
941 
942 	status = settings.Flatten(&file);
943 	if (status == B_OK) {
944 		// replace original settings file
945 		status = entry.Rename("BeMail Settings", true);
946 	} else
947 		entry.Remove();
948 
949 	return status;
950 }
951 
952 
953 status_t
954 TMailApp::LoadSettings()
955 {
956 	BMailSettings accountSettings;
957 	fDefaultAccount = accountSettings.DefaultOutboundAccount();
958 
959 	BPath path;
960 	status_t status = GetSettingsPath(path);
961 	if (status != B_OK)
962 		return status;
963 
964 	path.Append("BeMail Settings");
965 
966 	BFile file;
967 	status = file.SetTo(path.Path(), B_READ_ONLY);
968 	if (status != B_OK)
969 		return LoadOldSettings();
970 
971 	BMessage settings;
972 	status = settings.Unflatten(&file);
973 	if (status < B_OK || settings.what != 'BeMl') {
974 		// the current settings are corrupted, try old ones
975 		return LoadOldSettings();
976 	}
977 
978 	BRect rect;
979 	if (settings.FindRect("MailWindowSize", &rect) == B_OK)
980 		fMailWindowFrame = rect;
981 
982 	int32 int32Value;
983 //	if (settings.FindInt32("ExperienceLevel", &int32Value) == B_OK)
984 //		level = int32Value;
985 
986 	const char *fontFamily;
987 	if (settings.FindString("FontFamily", &fontFamily) == B_OK) {
988 		const char *fontStyle;
989 		if (settings.FindString("FontStyle", &fontStyle) == B_OK) {
990 			float size;
991 			if (settings.FindFloat("FontSize", &size) == B_OK) {
992 				if (size >= 7)
993 					fContentFont.SetSize(size);
994 
995 				if (fontFamily[0] && fontStyle[0]) {
996 					fContentFont.SetFamilyAndStyle(fontFamily[0] ? fontFamily : NULL,
997 						fontStyle[0] ? fontStyle : NULL);
998 				}
999 			}
1000 		}
1001 	}
1002 
1003 	if (settings.FindRect("SignatureWindowSize", &rect) == B_OK)
1004 		fSignatureWindowFrame = rect;
1005 
1006 	bool boolValue;
1007 	if (settings.FindBool("WordWrapMode", &boolValue) == B_OK)
1008 		fWrapMode = boolValue;
1009 
1010 	BPoint point;
1011 	if (settings.FindPoint("PreferencesWindowLocation", &point) == B_OK)
1012 		fPrefsWindowPos = point;
1013 
1014 	if (settings.FindBool("AutoMarkRead", &boolValue) == B_OK)
1015 		fAutoMarkRead = boolValue;
1016 
1017 	const char *string;
1018 	if (settings.FindString("SignatureText", &string) == B_OK) {
1019 		free(fSignature);
1020 		fSignature = strdup(string);
1021 	}
1022 
1023 	if (settings.FindInt32("CharacterSet", &int32Value) == B_OK)
1024 		fMailCharacterSet = int32Value;
1025 	if (fMailCharacterSet != B_MAIL_UTF8_CONVERSION
1026 		&& fMailCharacterSet != B_MAIL_US_ASCII_CONVERSION
1027 		&& BCharacterSetRoster::GetCharacterSetByConversionID(fMailCharacterSet) == NULL)
1028 		fMailCharacterSet = B_MS_WINDOWS_CONVERSION;
1029 
1030 	if (settings.FindString("FindString", &string) == B_OK)
1031 		FindWindow::SetFindString(string);
1032 
1033 	int8 int8Value;
1034 	if (settings.FindInt8("ShowButtonBar", &int8Value) == B_OK)
1035 		fShowButtonBar = int8Value;
1036 
1037 	if (settings.FindInt32("UseAccountFrom", &int32Value) == B_OK)
1038 		fUseAccountFrom = int32Value;
1039 	if (fUseAccountFrom < ACCOUNT_USE_DEFAULT
1040 		|| fUseAccountFrom > ACCOUNT_FROM_MAIL)
1041 		fUseAccountFrom = ACCOUNT_USE_DEFAULT;
1042 
1043 	if (settings.FindBool("ColoredQuotes", &boolValue) == B_OK)
1044 		fColoredQuotes = boolValue;
1045 
1046 	if (settings.FindString("ReplyPreamble", &string) == B_OK) {
1047 		free(fReplyPreamble);
1048 		fReplyPreamble = strdup(string);
1049 	}
1050 
1051 	if (settings.FindBool("AttachAttributes", &boolValue) == B_OK)
1052 		fAttachAttributes = boolValue;
1053 
1054 	if (settings.FindBool("WarnAboutUnencodableCharacters", &boolValue) == B_OK)
1055 		fWarnAboutUnencodableCharacters = boolValue;
1056 
1057 	if (settings.FindBool("StartWithSpellCheck", &boolValue) == B_OK)
1058 		fStartWithSpellCheckOn = boolValue;
1059 
1060 	return B_OK;
1061 }
1062 
1063 
1064 void
1065 TMailApp::FontChange()
1066 {
1067 	int32		index = 0;
1068 	BMessage	msg;
1069 	BWindow		*window;
1070 
1071 	msg.what = CHANGE_FONT;
1072 	msg.AddPointer("font", &fContentFont);
1073 
1074 	for (;;) {
1075 		window = WindowAt(index++);
1076 		if (!window)
1077 			break;
1078 
1079 		window->PostMessage(&msg);
1080 	}
1081 }
1082 
1083 
1084 TMailWindow*
1085 TMailApp::NewWindow(const entry_ref* ref, const char* to, bool resend,
1086 	BMessenger* trackerMessenger)
1087 {
1088 	BScreen screen(B_MAIN_SCREEN_ID);
1089 	BRect screenFrame = screen.Frame();
1090 
1091 	BRect r;
1092 	if (fMailWindowFrame.Width() < 64 || fMailWindowFrame.Height() < 20) {
1093 		// default size
1094 		r.Set(6, TITLE_BAR_HEIGHT, 6 + WIND_WIDTH,
1095 			TITLE_BAR_HEIGHT + WIND_HEIGHT);
1096 	} else
1097 		r = fMailWindowFrame;
1098 
1099 	// make sure the window is not larger than the screen space
1100 	if (r.Height() > screenFrame.Height())
1101 		r.bottom = r.top + screenFrame.Height();
1102 	if (r.Width() > screenFrame.Width())
1103 		r.bottom = r.top + screenFrame.Width();
1104 
1105 	// cascading windows
1106 	r.OffsetBy(((fWindowCount + 5) % 10) * 15 - 75,
1107 		((fWindowCount + 5) % 10) * 15 - 75);
1108 
1109 	// make sure the window is still on screen
1110 	if (r.left - 6 < screenFrame.left)
1111 		r.OffsetTo(screenFrame.left + 8, r.top);
1112 
1113 	if (r.left + 20 > screenFrame.right)
1114 		r.OffsetTo(6, r.top);
1115 
1116 	if (r.top - 26 < screenFrame.top)
1117 		r.OffsetTo(r.left, screenFrame.top + 26);
1118 
1119 	if (r.top + 20 > screenFrame.bottom)
1120 		r.OffsetTo(r.left, TITLE_BAR_HEIGHT);
1121 
1122 	if (r.Width() < WIND_WIDTH)
1123 		r.right = r.left + WIND_WIDTH;
1124 
1125 	fWindowCount++;
1126 
1127 	BString title;
1128 	BFile file;
1129 	if (!resend && ref && file.SetTo(ref, O_RDONLY) == B_OK) {
1130 		BString name;
1131 		if (file.ReadAttrString(B_MAIL_ATTR_NAME, &name) == B_OK) {
1132 			title << name;
1133 			BString subject;
1134 			if (file.ReadAttrString(B_MAIL_ATTR_SUBJECT, &subject) == B_OK)
1135 				title << " -> " << subject;
1136 		}
1137 	}
1138 	if (title == "")
1139 		title = B_TRANSLATE_SYSTEM_NAME("Mail");
1140 
1141 	TMailWindow* window = new TMailWindow(r, title.String(), this, ref, to,
1142 		&fContentFont, resend, trackerMessenger);
1143 	fWindowList.AddItem(window);
1144 
1145 	return window;
1146 }
1147 
1148 
1149 // #pragma mark - settings
1150 
1151 
1152 bool
1153 TMailApp::AutoMarkRead()
1154 {
1155 	BAutolock _(this);
1156 	return fAutoMarkRead;
1157 }
1158 
1159 
1160 BString
1161 TMailApp::Signature()
1162 {
1163 	BAutolock _(this);
1164 	return BString(fSignature);
1165 }
1166 
1167 
1168 BString
1169 TMailApp::ReplyPreamble()
1170 {
1171 	BAutolock _(this);
1172 	return BString(fReplyPreamble);
1173 }
1174 
1175 
1176 bool
1177 TMailApp::WrapMode()
1178 {
1179 	BAutolock _(this);
1180 	return fWrapMode;
1181 }
1182 
1183 
1184 bool
1185 TMailApp::AttachAttributes()
1186 {
1187 	BAutolock _(this);
1188 	return fAttachAttributes;
1189 }
1190 
1191 
1192 bool
1193 TMailApp::ColoredQuotes()
1194 {
1195 	BAutolock _(this);
1196 	return fColoredQuotes;
1197 }
1198 
1199 
1200 uint8
1201 TMailApp::ShowButtonBar()
1202 {
1203 	BAutolock _(this);
1204 	return fShowButtonBar;
1205 }
1206 
1207 
1208 bool
1209 TMailApp::WarnAboutUnencodableCharacters()
1210 {
1211 	BAutolock _(this);
1212 	return fWarnAboutUnencodableCharacters;
1213 }
1214 
1215 
1216 bool
1217 TMailApp::StartWithSpellCheckOn()
1218 {
1219 	BAutolock _(this);
1220 	return fStartWithSpellCheckOn;
1221 }
1222 
1223 
1224 void
1225 TMailApp::SetDefaultAccount(int32 account)
1226 {
1227 	BAutolock _(this);
1228 	fDefaultAccount = account;
1229 }
1230 
1231 
1232 int32
1233 TMailApp::DefaultAccount()
1234 {
1235 	BAutolock _(this);
1236 	return fDefaultAccount;
1237 }
1238 
1239 
1240 int32
1241 TMailApp::UseAccountFrom()
1242 {
1243 	BAutolock _(this);
1244 	return fUseAccountFrom;
1245 }
1246 
1247 
1248 uint32
1249 TMailApp::MailCharacterSet()
1250 {
1251 	BAutolock _(this);
1252 	return fMailCharacterSet;
1253 }
1254 
1255 
1256 BFont
1257 TMailApp::ContentFont()
1258 {
1259 	BAutolock _(this);
1260 	return fContentFont;
1261 }
1262 
1263 
1264 //	#pragma mark -
1265 
1266 
1267 int
1268 main()
1269 {
1270 	tzset();
1271 
1272 	TMailApp().Run();
1273 	return B_OK;
1274 }
1275 
1276