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