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