1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2001, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
23CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or
30registered trademarks of Be Incorporated in the United States and other
31countries. Other brand product names are registered trademarks or trademarks
32of their respective holders. All rights reserved.
33*/
34
35
36#include <stdlib.h>
37#include <string.h>
38#include <stdio.h>
39#include <ctype.h>
40
41#include <Alert.h>
42#include <Beep.h>
43#include <Clipboard.h>
44#include <Debug.h>
45#include <E-mail.h>
46#include <Input.h>
47#include <Locale.h>
48#include <MenuItem.h>
49#include <Mime.h>
50#include <NodeInfo.h>
51#include <NodeMonitor.h>
52#include <Path.h>
53#include <PopUpMenu.h>
54#include <Region.h>
55#include <Roster.h>
56#include <ScrollView.h>
57#include <TextView.h>
58#include <UTF8.h>
59
60#include <MailMessage.h>
61#include <MailAttachment.h>
62#include <mail_util.h>
63
64#include "MailApp.h"
65#include "MailSupport.h"
66#include "MailWindow.h"
67#include "Messages.h"
68#include "Content.h"
69#include "Utilities.h"
70#include "FieldMsg.h"
71#include "Words.h"
72
73
74#define DEBUG_SPELLCHECK 0
75#if DEBUG_SPELLCHECK
76#	define DSPELL(x) x
77#else
78#	define DSPELL(x) ;
79#endif
80
81
82#define B_TRANSLATION_CONTEXT "Mail"
83
84
85const rgb_color kNormalTextColor = {0, 0, 0, 255};
86const rgb_color kSpellTextColor = {255, 0, 0, 255};
87const rgb_color kHyperLinkColor = {0, 0, 255, 255};
88const rgb_color kHeaderColor = {72, 72, 72, 255};
89
90const rgb_color kQuoteColors[] = {
91	{0, 0, 0x80, 0},		// 3rd, 6th, ... quote level color (blue)
92	{0, 0x80, 0, 0},		// 1st, 4th, ... quote level color (green)
93	{0x80, 0, 0, 0}			// 2nd, ... (red)
94};
95const int32 kNumQuoteColors = 3;
96
97const rgb_color kDiffColors[] = {
98	{0xb0, 0, 0, 0},		// '-', red
99	{0, 0x90, 0, 0},		// '+', green
100	{0x6a, 0x6a, 0x6a, 0}	// '@@', dark grey
101};
102
103void Unicode2UTF8(int32 c, char **out);
104
105
106inline bool
107IsInitialUTF8Byte(uchar b)
108{
109	return ((b & 0xC0) != 0x80);
110}
111
112
113void
114Unicode2UTF8(int32 c, char **out)
115{
116	char *s = *out;
117
118	ASSERT(c < 0x200000);
119
120	if (c < 0x80)
121		*(s++) = c;
122	else if (c < 0x800) {
123		*(s++) = 0xc0 | (c >> 6);
124		*(s++) = 0x80 | (c & 0x3f);
125	} else if (c < 0x10000) {
126		*(s++) = 0xe0 | (c >> 12);
127		*(s++) = 0x80 | ((c >> 6) & 0x3f);
128		*(s++) = 0x80 | (c & 0x3f);
129	} else if (c < 0x200000) {
130		*(s++) = 0xf0 | (c >> 18);
131		*(s++) = 0x80 | ((c >> 12) & 0x3f);
132		*(s++) = 0x80 | ((c >> 6) & 0x3f);
133		*(s++) = 0x80 | (c & 0x3f);
134	}
135	*out = s;
136}
137
138
139static bool
140FilterHTMLTag(int32 &first, char **t, char *end)
141{
142	const char *newlineTags[] = {
143		"br", "/p", "/div", "/table", "/tr",
144		NULL};
145
146	char *a = *t;
147
148	// check for some common entities (in ISO-Latin-1)
149	if (first == '&') {
150		// filter out and convert decimal values
151		if (a[1] == '#' && sscanf(a + 2, "%ld;", &first) == 1) {
152			t[0] += strchr(a, ';') - a;
153			return false;
154		}
155
156		const struct { const char *name; int32 code; } entities[] = {
157			// this list is sorted alphabetically to be binary searchable
158			// the current implementation doesn't do this, though
159
160			// "name" is the entity name,
161			// "code" is the corresponding unicode
162			{"AElig;",	0x00c6},
163			{"Aacute;",	0x00c1},
164			{"Acirc;",	0x00c2},
165			{"Agrave;",	0x00c0},
166			{"Aring;",	0x00c5},
167			{"Atilde;",	0x00c3},
168			{"Auml;",	0x00c4},
169			{"Ccedil;",	0x00c7},
170			{"Eacute;",	0x00c9},
171			{"Ecirc;",	0x00ca},
172			{"Egrave;",	0x00c8},
173			{"Euml;",	0x00cb},
174			{"Iacute;", 0x00cd},
175			{"Icirc;",	0x00ce},
176			{"Igrave;", 0x00cc},
177			{"Iuml;",	0x00cf},
178			{"Ntilde;",	0x00d1},
179			{"Oacute;", 0x00d3},
180			{"Ocirc;",	0x00d4},
181			{"Ograve;", 0x00d2},
182			{"Ouml;",	0x00d6},
183			{"Uacute;", 0x00da},
184			{"Ucirc;",	0x00db},
185			{"Ugrave;", 0x00d9},
186			{"Uuml;",	0x00dc},
187			{"aacute;", 0x00e1},
188			{"acirc;",	0x00e2},
189			{"aelig;",	0x00e6},
190			{"agrave;", 0x00e0},
191			{"amp;",	'&'},
192			{"aring;",	0x00e5},
193			{"atilde;", 0x00e3},
194			{"auml;",	0x00e4},
195			{"ccedil;",	0x00e7},
196			{"copy;",	0x00a9},
197			{"eacute;",	0x00e9},
198			{"ecirc;",	0x00ea},
199			{"egrave;",	0x00e8},
200			{"euml;",	0x00eb},
201			{"gt;",		'>'},
202			{"iacute;", 0x00ed},
203			{"icirc;",	0x00ee},
204			{"igrave;", 0x00ec},
205			{"iuml;",	0x00ef},
206			{"lt;",		'<'},
207			{"nbsp;",	' '},
208			{"ntilde;",	0x00f1},
209			{"oacute;", 0x00f3},
210			{"ocirc;",	0x00f4},
211			{"ograve;", 0x00f2},
212			{"ouml;",	0x00f6},
213			{"quot;",	'"'},
214			{"szlig;",	0x00f6},
215			{"uacute;", 0x00fa},
216			{"ucirc;",	0x00fb},
217			{"ugrave;", 0x00f9},
218			{"uuml;",	0x00fc},
219			{NULL, 0}
220		};
221
222		for (int32 i = 0; entities[i].name; i++) {
223			// entities are case-sensitive
224			int32 length = strlen(entities[i].name);
225			if (!strncmp(a + 1, entities[i].name, length)) {
226				t[0] += length;	// note that the '&' is included here
227				first = entities[i].code;
228				return false;
229			}
230		}
231	}
232
233	// no tag to filter
234	if (first != '<')
235		return false;
236
237	a++;
238
239	// is the tag one of the newline tags?
240
241	bool newline = false;
242	for (int i = 0; newlineTags[i]; i++) {
243		int length = strlen(newlineTags[i]);
244		if (!strncasecmp(a, (char *)newlineTags[i], length) && !isalnum(a[length])) {
245			newline = true;
246			break;
247		}
248	}
249
250	// oh, it's not, so skip it!
251
252	if (!strncasecmp(a, "head", 4)) {	// skip "head" completely
253		for (; a[0] && a < end; a++) {
254			// Find the end of the HEAD section, or the start of the BODY,
255			// which happens for some malformed spam.
256			if (strncasecmp (a, "</head", 6) == 0 ||
257				strncasecmp (a, "<body", 5) == 0)
258				break;
259		}
260	}
261
262	// skip until tag end
263	while (a[0] && a[0] != '>' && a < end)
264		a++;
265
266	t[0] = a;
267
268	if (newline) {
269		first = '\n';
270		return false;
271	}
272
273	return true;
274}
275
276
277/** Returns the type and length of the URL in the string if it is one.
278 *	If the "url" string is specified, it will fill it with the complete
279 *	URL that might differ from the one in the text itself (i.e. because
280 *	of an prepended "http://").
281 */
282
283static uint8
284CheckForURL(const char *string, size_t &urlLength, BString *url = NULL)
285{
286	const char *urlPrefixes[] = {
287		"http://",
288		"ftp://",
289		"shttp://",
290		"https://",
291		"finger://",
292		"telnet://",
293		"gopher://",
294		"news://",
295		"nntp://",
296		"file://",
297		NULL
298	};
299
300	//
301	//	Search for URL prefix
302	//
303	uint8 type = 0;
304	for (const char **prefix = urlPrefixes; *prefix != 0; prefix++) {
305		if (!cistrncmp(string, *prefix, strlen(*prefix))) {
306			type = TYPE_URL;
307			break;
308		}
309	}
310
311	//
312	//	Not a URL? check for "mailto:" or "www."
313	//
314	if (type == 0 && !cistrncmp(string, "mailto:", 7))
315		type = TYPE_MAILTO;
316	if (type == 0 && !strncmp(string, "www.", 4)) {
317		// this type will be special cased later (and a http:// is added
318		// for the enclosure address)
319		type = TYPE_URL;
320	}
321	if (type == 0) {
322		// check for valid eMail addresses
323		const char *at = strchr(string, '@');
324		if (at) {
325			const char *pos = string;
326			bool readName = false;
327			for (; pos < at; pos++) {
328				// ToDo: are these all allowed characters?
329				if (!isalnum(pos[0]) && pos[0] != '_' && pos[0] != '.' && pos[0] != '-')
330					break;
331
332				readName = true;
333			}
334			if (pos == at && readName)
335				type = TYPE_MAILTO;
336		}
337	}
338
339	if (type == 0)
340		return 0;
341
342	int32 index = strcspn(string, " \t<>)\"\\,\r\n");
343
344	// filter out some punctuation marks if they are the last character
345	char suffix = string[index - 1];
346	if (suffix == '.'
347		|| suffix == ','
348		|| suffix == '?'
349		|| suffix == '!'
350		|| suffix == ':'
351		|| suffix == ';')
352		index--;
353
354	if (type == TYPE_URL) {
355		char *parenthesis = NULL;
356
357		// filter out a trailing ')' if there is no left parenthesis before
358		if (string[index - 1] == ')') {
359			char *parenthesis = strchr(string, '(');
360			if (parenthesis == NULL || parenthesis > string + index)
361				index--;
362		}
363
364		// filter out a trailing ']' if there is no left bracket before
365		if (parenthesis == NULL && string[index - 1] == ']') {
366			char *parenthesis = strchr(string, '[');
367			if (parenthesis == NULL || parenthesis > string + index)
368				index--;
369		}
370	}
371
372	if (url != NULL) {
373		// copy the address to the specified string
374		if (type == TYPE_URL && string[0] == 'w') {
375			// URL starts with "www.", so add the protocol to it
376			url->SetTo("http://");
377			url->Append(string, index);
378		} else if (type == TYPE_MAILTO && cistrncmp(string, "mailto:", 7)) {
379			// eMail address had no "mailto:" prefix
380			url->SetTo("mailto:");
381			url->Append(string, index);
382		} else
383			url->SetTo(string, index);
384	}
385	urlLength = index;
386
387	return type;
388}
389
390
391static void
392CopyQuotes(const char *text, size_t length, char *outText, size_t &outLength)
393{
394	// count qoute level (to be able to wrap quotes correctly)
395
396	const char *quote = QUOTE;
397	int32 level = 0;
398	for (size_t i = 0; i < length; i++) {
399		if (text[i] == quote[0])
400			level++;
401		else if (text[i] != ' ' && text[i] != '\t')
402			break;
403	}
404
405	// if there are too much quotes, try to preserve the quote color level
406	if (level > 10)
407		level = kNumQuoteColors * 3 + (level % kNumQuoteColors);
408
409	// copy the quotes to outText
410
411	const int32 quoteLength = strlen(QUOTE);
412	outLength = 0;
413	while (level-- > 0) {
414		strcpy(outText + outLength, QUOTE);
415		outLength += quoteLength;
416	}
417}
418
419
420int32
421diff_mode(char c)
422{
423	if (c == '+')
424		return 2;
425	if (c == '-')
426		return 1;
427	if (c == '@')
428		return 3;
429	if (c == ' ')
430		return 0;
431
432	// everything else ends the diff mode
433	return -1;
434}
435
436
437bool
438is_quote_char(char c)
439{
440	return c == '>' || c == '|';
441}
442
443
444/*!	Fills the specified text_run_array with the correct values for the
445	specified text.
446	If "view" is NULL, it will assume that "line" lies on a line break,
447	if not, it will correctly retrieve the number of quotes the current
448	line already has.
449*/
450void
451FillInQuoteTextRuns(BTextView* view, quote_context* context, const char* line,
452	int32 length, const BFont& font, text_run_array* style, int32 maxStyles)
453{
454	text_run* runs = style->runs;
455	int32 index = style->count;
456	bool begin;
457	int32 pos = 0;
458	int32 diffMode = 0;
459	bool inDiff = false;
460	bool wasDiff = false;
461	int32 level = 0;
462
463	// get index to the beginning of the current line
464
465	if (context != NULL) {
466		level = context->level;
467		diffMode = context->diff_mode;
468		begin = context->begin;
469		inDiff = context->in_diff;
470		wasDiff = context->was_diff;
471	} else if (view != NULL) {
472		int32 start, end;
473		view->GetSelection(&end, &end);
474
475		begin = view->TextLength() == 0
476			|| view->ByteAt(view->TextLength() - 1) == '\n';
477
478		// the following line works only reliable when text wrapping is set to
479		// off; so the complicated version actually used here is necessary:
480		// start = view->OffsetAt(view->CurrentLine());
481
482		const char *text = view->Text();
483
484		if (!begin) {
485			// if the text is not the start of a new line, go back
486			// to the first character in the current line
487			for (start = end; start > 0; start--) {
488				if (text[start - 1] == '\n')
489					break;
490			}
491		}
492
493		// get number of nested qoutes for current line
494
495		if (!begin && start < end) {
496			begin = true;
497				// if there was no text in this line, there may come
498				// more nested quotes
499
500			diffMode = diff_mode(text[start]);
501			if (diffMode == 0) {
502				for (int32 i = start; i < end; i++) {
503					if (is_quote_char(text[i]))
504						level++;
505					else if (text[i] != ' ' && text[i] != '\t') {
506						begin = false;
507						break;
508					}
509				}
510			} else
511				inDiff = true;
512
513			if (begin) {
514				// skip leading spaces (tabs & newlines aren't allowed here)
515				while (line[pos] == ' ')
516					pos++;
517			}
518		}
519	} else
520		begin = true;
521
522	// set styles for all qoute levels in the text to be inserted
523
524	for (int32 pos = 0; pos < length;) {
525		int32 next;
526		if (begin && is_quote_char(line[pos])) {
527			begin = false;
528
529			while (pos < length && line[pos] != '\n') {
530				// insert style for each quote level
531				level++;
532
533				bool search = true;
534				for (next = pos + 1; next < length; next++) {
535					if ((search && is_quote_char(line[next]))
536						|| line[next] == '\n')
537						break;
538					else if (search && line[next] != ' ' && line[next] != '\t')
539						search = false;
540				}
541
542				runs[index].offset = pos;
543				runs[index].font = font;
544				runs[index].color = level > 0
545					? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor;
546
547				pos = next;
548				if (++index >= maxStyles)
549					break;
550			}
551		} else {
552			if (begin) {
553				if (!inDiff) {
554					inDiff = !strncmp(&line[pos], "--- ", 4);
555					wasDiff = false;
556				}
557				if (inDiff) {
558					diffMode = diff_mode(line[pos]);
559					if (diffMode < 0) {
560						inDiff = false;
561						wasDiff = true;
562					}
563				}
564			}
565
566			runs[index].offset = pos;
567			runs[index].font = font;
568			if (wasDiff)
569				runs[index].color = kDiffColors[diff_mode('@') - 1];
570			else if (diffMode <= 0) {
571				runs[index].color = level > 0
572					? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor;
573			} else
574				runs[index].color = kDiffColors[diffMode - 1];
575
576			begin = false;
577
578			for (next = pos; next < length; next++) {
579				if (line[next] == '\n') {
580					begin = true;
581					wasDiff = false;
582					break;
583				}
584			}
585
586			pos = next;
587			index++;
588		}
589
590		if (pos < length)
591			begin = line[pos] == '\n';
592
593		if (begin) {
594			pos++;
595			level = 0;
596			wasDiff = false;
597
598			// skip one leading space (tabs & newlines aren't allowed here)
599			if (!inDiff && pos < length && line[pos] == ' ')
600				pos++;
601		}
602
603		if (index >= maxStyles)
604			break;
605	}
606	style->count = index;
607
608	if (context) {
609		// update context for next run
610		context->level = level;
611		context->diff_mode = diffMode;
612		context->begin = begin;
613		context->in_diff = inDiff;
614		context->was_diff = wasDiff;
615	}
616}
617
618
619//	#pragma mark -
620
621
622TextRunArray::TextRunArray(size_t entries)
623	:
624	fNumEntries(entries)
625{
626	fArray = (text_run_array *)malloc(sizeof(int32) + sizeof(text_run) * entries);
627	if (fArray != NULL)
628		fArray->count = 0;
629}
630
631
632TextRunArray::~TextRunArray()
633{
634	free(fArray);
635}
636
637
638//====================================================================
639//	#pragma mark -
640
641
642TContentView::TContentView(BRect rect, bool incoming, BFont* font,
643	bool showHeader, bool coloredQuotes)
644	:
645	BView(rect, "m_content", B_FOLLOW_ALL,
646		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
647
648	fFocus(false),
649	fIncoming(incoming)
650{
651	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
652	fOffset = 12;
653
654	BRect r(rect);
655	r.OffsetTo(0, 0);
656	r.right -= B_V_SCROLL_BAR_WIDTH;
657	r.bottom -= B_H_SCROLL_BAR_HEIGHT;
658	r.top += 4;
659	BRect text(r);
660	text.OffsetTo(0, 0);
661	text.InsetBy(5, 5);
662
663	fTextView = new TTextView(r, text, fIncoming, this, font, showHeader,
664		coloredQuotes);
665	BScrollView *scroll = new BScrollView("", fTextView, B_FOLLOW_ALL, 0, true, true);
666	AddChild(scroll);
667}
668
669
670void
671TContentView::MessageReceived(BMessage *msg)
672{
673	switch (msg->what) {
674		case CHANGE_FONT:
675		{
676			BFont *font;
677			msg->FindPointer("font", (void **)&font);
678			fTextView->UpdateFont(font);
679			fTextView->Invalidate(Bounds());
680			break;
681		}
682
683		case M_QUOTE:
684		{
685			int32 start, finish;
686			fTextView->GetSelection(&start, &finish);
687			fTextView->AddQuote(start, finish);
688			break;
689		}
690		case M_REMOVE_QUOTE:
691		{
692			int32 start, finish;
693			fTextView->GetSelection(&start, &finish);
694			fTextView->RemoveQuote(start, finish);
695			break;
696		}
697
698		case M_SIGNATURE:
699		{
700			if (fTextView->IsReaderThreadRunning()) {
701				// Do not add the signature until the reader thread
702				// is finished. Resubmit the message for later processing
703				Window()->PostMessage(msg);
704				break;
705			}
706
707			entry_ref ref;
708			msg->FindRef("ref", &ref);
709
710			BFile file(&ref, B_READ_ONLY);
711			if (file.InitCheck() == B_OK) {
712				int32 start, finish;
713				fTextView->GetSelection(&start, &finish);
714
715				off_t size;
716				file.GetSize(&size);
717				if (size > 32768)	// safety against corrupt signatures
718					break;
719
720				char *signature = (char *)malloc(size);
721				if (signature == NULL)
722					break;
723				ssize_t bytesRead = file.Read(signature, size);
724				if (bytesRead < B_OK) {
725					free (signature);
726					break;
727				}
728
729				const char *text = fTextView->Text();
730				int32 length = fTextView->TextLength();
731
732				// reserve some empty lines before the signature
733				const char* newLines = "\n\n\n\n";
734				if (length && text[length - 1] == '\n')
735					newLines++;
736
737				fTextView->Select(length, length);
738				fTextView->Insert(newLines, strlen(newLines));
739				length += strlen(newLines);
740
741				// append the signature
742				fTextView->Select(length, length);
743				fTextView->Insert(signature, bytesRead);
744				fTextView->Select(length, length + bytesRead);
745				fTextView->ScrollToSelection();
746
747				// set the editing cursor position
748				fTextView->Select(length - 2 , length - 2);
749				fTextView->ScrollToSelection();
750				free (signature);
751			} else {
752				beep();
753				BAlert* alert = new BAlert("",
754					B_TRANSLATE("An error occurred trying to open this "
755						"signature."), B_TRANSLATE("Sorry"));
756				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
757				alert->Go();
758			}
759			break;
760		}
761
762		case M_FIND:
763			FindString(msg->FindString("findthis"));
764			break;
765
766		default:
767			BView::MessageReceived(msg);
768	}
769}
770
771
772void
773TContentView::FindString(const char *str)
774{
775	int32	finish;
776	int32	pass = 0;
777	int32	start = 0;
778
779	if (str == NULL)
780		return;
781
782	//
783	//	Start from current selection or from the beginning of the pool
784	//
785	const char *text = fTextView->Text();
786	int32 count = fTextView->TextLength();
787	fTextView->GetSelection(&start, &finish);
788	if (start != finish)
789		start = finish;
790	if (!count || text == NULL)
791		return;
792
793	//
794	//	Do the find
795	//
796	while (pass < 2) {
797		long found = -1;
798		char lc = tolower(str[0]);
799		char uc = toupper(str[0]);
800		for (long i = start; i < count; i++) {
801			if (text[i] == lc || text[i] == uc) {
802				const char *s = str;
803				const char *t = text + i;
804				while (*s && (tolower(*s) == tolower(*t))) {
805					s++;
806					t++;
807				}
808				if (*s == 0) {
809					found = i;
810					break;
811				}
812			}
813		}
814
815		//
816		//	Select the text if it worked
817		//
818		if (found != -1) {
819			Window()->Activate();
820			fTextView->Select(found, found + strlen(str));
821			fTextView->ScrollToSelection();
822			fTextView->MakeFocus(true);
823			return;
824		}
825		else if (start) {
826			start = 0;
827			text = fTextView->Text();
828			count = fTextView->TextLength();
829			pass++;
830		} else {
831			beep();
832			return;
833		}
834	}
835}
836
837
838void
839TContentView::Focus(bool focus)
840{
841	if (fFocus != focus) {
842		fFocus = focus;
843		Draw(Frame());
844	}
845}
846
847
848void
849TContentView::FrameResized(float /* width */, float /* height */)
850{
851	BRect r(fTextView->Bounds());
852	r.OffsetTo(0, 0);
853	r.InsetBy(5, 5);
854	fTextView->SetTextRect(r);
855}
856
857
858//====================================================================
859//	#pragma mark -
860
861
862TTextView::TTextView(BRect frame, BRect text, bool incoming, TContentView *view,
863	BFont *font, bool showHeader, bool coloredQuotes)
864	:
865	BTextView(frame, "", text, B_FOLLOW_ALL, B_WILL_DRAW | B_NAVIGABLE),
866
867	fHeader(showHeader),
868	fColoredQuotes(coloredQuotes),
869	fReady(false),
870	fYankBuffer(NULL),
871	fLastPosition(-1),
872	fMail(NULL),
873	fFont(font),
874	fParent(view),
875	fStopLoading(false),
876	fThread(0),
877	fPanel(NULL),
878	fIncoming(incoming),
879	fSpellCheck(false),
880	fRaw(false),
881	fCursor(false),
882	fFirstSpellMark(NULL)
883{
884	fStopSem = create_sem(1, "reader_sem");
885	SetStylable(true);
886
887	fEnclosures = new BList();
888
889	// Enclosure pop up menu
890	fEnclosureMenu = new BPopUpMenu("Enclosure", false, false);
891	fEnclosureMenu->SetFont(be_plain_font);
892	fEnclosureMenu->AddItem(new BMenuItem(
893		B_TRANSLATE("Save attachment" B_UTF8_ELLIPSIS),	new BMessage(M_SAVE)));
894	fEnclosureMenu->AddItem(new BMenuItem(B_TRANSLATE("Open attachment"),
895		new BMessage(M_OPEN)));
896
897	// Hyperlink pop up menu
898	fLinkMenu = new BPopUpMenu("Link", false, false);
899	fLinkMenu->SetFont(be_plain_font);
900	fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Open this link"),
901		new BMessage(M_OPEN)));
902	fLinkMenu->AddItem(new BMenuItem(B_TRANSLATE("Copy link location"),
903		new BMessage(M_COPY)));
904
905	SetDoesUndo(true);
906
907	//Undo function
908	fUndoBuffer.On();
909	fInputMethodUndoBuffer.On();
910	fUndoState.replaced = false;
911	fUndoState.deleted = false;
912	fInputMethodUndoState.active = false;
913	fInputMethodUndoState.replace = false;
914}
915
916
917TTextView::~TTextView()
918{
919	ClearList();
920	delete fPanel;
921
922	if (fYankBuffer)
923		free(fYankBuffer);
924
925	delete_sem(fStopSem);
926}
927
928
929void
930TTextView::UpdateFont(const BFont* newFont)
931{
932	fFont = *newFont;
933
934	// update the text run array safely with new font
935	text_run_array *runArray = RunArray(0, LONG_MAX);
936	for (int i = 0; i < runArray->count; i++)
937		runArray->runs[i].font = *newFont;
938
939	SetRunArray(0, LONG_MAX, runArray);
940	FreeRunArray(runArray);
941}
942
943
944void
945TTextView::AttachedToWindow()
946{
947	BTextView::AttachedToWindow();
948	fFont.SetSpacing(B_FIXED_SPACING);
949	SetFontAndColor(&fFont);
950
951	if (fMail != NULL) {
952		LoadMessage(fMail, false, NULL);
953		if (fIncoming)
954			MakeEditable(false);
955	}
956}
957
958
959void
960TTextView::KeyDown(const char *key, int32 count)
961{
962	char		raw;
963	int32		end;
964	int32 		start;
965	uint32		mods;
966	BMessage	*msg;
967	int32		textLen = TextLength();
968
969	msg = Window()->CurrentMessage();
970	mods = msg->FindInt32("modifiers");
971
972	switch (key[0]) {
973		case B_HOME:
974			if (IsSelectable()) {
975				if (IsEditable())
976					BTextView::KeyDown(key, count);
977				else {
978					// scroll to the beginning
979					Select(0, 0);
980					ScrollToSelection();
981				}
982			}
983			break;
984
985		case B_END:
986			if (IsSelectable()) {
987				if (IsEditable())
988					BTextView::KeyDown(key, count);
989				else {
990					// scroll to the end
991					int32 length = TextLength();
992					Select(length, length);
993					ScrollToSelection();
994				}
995			}
996			break;
997
998		case 0x02:						// ^b - back 1 char
999			if (IsSelectable()) {
1000				GetSelection(&start, &end);
1001				while (!IsInitialUTF8Byte(ByteAt(--start))) {
1002					if (start < 0) {
1003						start = 0;
1004						break;
1005					}
1006				}
1007				if (start >= 0) {
1008					Select(start, start);
1009					ScrollToSelection();
1010				}
1011			}
1012			break;
1013
1014		case B_DELETE:
1015			if (IsSelectable()) {
1016				if ((key[0] == B_DELETE) || (mods & B_CONTROL_KEY)) {
1017					// ^d
1018					if (IsEditable()) {
1019						GetSelection(&start, &end);
1020						if (start != end)
1021							Delete();
1022						else {
1023							for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) {
1024								if (end > textLen) {
1025									end = textLen;
1026									break;
1027								}
1028							}
1029							Select(start, end);
1030							Delete();
1031						}
1032					}
1033				}
1034				else
1035					Select(textLen, textLen);
1036				ScrollToSelection();
1037			}
1038			break;
1039
1040		case 0x05:						// ^e - end of line
1041			if (IsSelectable() && (mods & B_CONTROL_KEY)) {
1042				if (CurrentLine() == CountLines() - 1)
1043					Select(TextLength(), TextLength());
1044				else {
1045					GoToLine(CurrentLine() + 1);
1046					GetSelection(&start, &end);
1047					Select(start - 1, start - 1);
1048				}
1049			}
1050			break;
1051
1052		case 0x06:						// ^f - forward 1 char
1053			if (IsSelectable()) {
1054				GetSelection(&start, &end);
1055				if (end > start)
1056					start = end;
1057				else {
1058					for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end));
1059						end++) {
1060						if (end > textLen) {
1061							end = textLen;
1062							break;
1063						}
1064					}
1065					start = end;
1066				}
1067				Select(start, start);
1068				ScrollToSelection();
1069			}
1070			break;
1071
1072		case 0x0e:						// ^n - next line
1073			if (IsSelectable()) {
1074				raw = B_DOWN_ARROW;
1075				BTextView::KeyDown(&raw, 1);
1076			}
1077			break;
1078
1079		case 0x0f:						// ^o - open line
1080			if (IsEditable()) {
1081				GetSelection(&start, &end);
1082				Delete();
1083
1084				char newLine = '\n';
1085				Insert(&newLine, 1);
1086				Select(start, start);
1087				ScrollToSelection();
1088			}
1089			break;
1090
1091		case B_PAGE_UP:
1092			if (mods & B_CONTROL_KEY) {	// ^k kill text from cursor to e-o-line
1093				if (IsEditable()) {
1094					GetSelection(&start, &end);
1095					if ((start != fLastPosition) && (fYankBuffer)) {
1096						free(fYankBuffer);
1097						fYankBuffer = NULL;
1098					}
1099					fLastPosition = start;
1100					if (CurrentLine() < CountLines() - 1) {
1101						GoToLine(CurrentLine() + 1);
1102						GetSelection(&end, &end);
1103						end--;
1104					}
1105					else
1106						end = TextLength();
1107					if (end < start)
1108						break;
1109					if (start == end)
1110						end++;
1111					Select(start, end);
1112					if (fYankBuffer) {
1113						fYankBuffer = (char *)realloc(fYankBuffer,
1114									 strlen(fYankBuffer) + (end - start) + 1);
1115						GetText(start, end - start,
1116							    &fYankBuffer[strlen(fYankBuffer)]);
1117					} else {
1118						fYankBuffer = (char *)malloc(end - start + 1);
1119						GetText(start, end - start, fYankBuffer);
1120					}
1121					Delete();
1122					ScrollToSelection();
1123				}
1124				break;
1125			}
1126
1127			BTextView::KeyDown(key, count);
1128			break;
1129
1130		case 0x10:						// ^p goto previous line
1131			if (IsSelectable()) {
1132				raw = B_UP_ARROW;
1133				BTextView::KeyDown(&raw, 1);
1134			}
1135			break;
1136
1137		case 0x19:						// ^y yank text
1138			if (IsEditable() && fYankBuffer) {
1139				Delete();
1140				Insert(fYankBuffer);
1141				ScrollToSelection();
1142			}
1143			break;
1144
1145		default:
1146			BTextView::KeyDown(key, count);
1147	}
1148}
1149
1150
1151void
1152TTextView::MakeFocus(bool focus)
1153{
1154	if (!focus) {
1155		// ToDo: can someone please translate this? Otherwise I will remove it - axeld.
1156		// MakeFocus(false) は、IM も Inactive になり、そのまま確定される。
1157		// しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED)
1158		// を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。
1159		fInputMethodUndoState.active = false;
1160		// fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加
1161		if (fInputMethodUndoBuffer.CountItems() > 0) {
1162			KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1);
1163			if (item->History == K_INSERTED) {
1164				fUndoBuffer.MakeNewUndoItem();
1165				fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, item->History, item->CursorPos);
1166				fUndoBuffer.MakeNewUndoItem();
1167			}
1168			fInputMethodUndoBuffer.MakeEmpty();
1169		}
1170	}
1171	BTextView::MakeFocus(focus);
1172
1173	fParent->Focus(focus);
1174}
1175
1176
1177void
1178TTextView::MessageReceived(BMessage *msg)
1179{
1180	switch (msg->what) {
1181		case B_SIMPLE_DATA:
1182		{
1183			if (fIncoming)
1184				break;
1185
1186			BMessage message(REFS_RECEIVED);
1187			bool isEnclosure = false;
1188			bool inserted = false;
1189
1190			off_t len = 0;
1191			int32 end;
1192			int32 start;
1193
1194			int32 index = 0;
1195			entry_ref ref;
1196			while (msg->FindRef("refs", index++, &ref) == B_OK) {
1197				BFile file(&ref, B_READ_ONLY);
1198				if (file.InitCheck() == B_OK) {
1199					BNodeInfo node(&file);
1200					char type[B_FILE_NAME_LENGTH];
1201					node.GetType(type);
1202
1203					off_t size = 0;
1204					file.GetSize(&size);
1205
1206					if (!strncasecmp(type, "text/", 5) && size > 0) {
1207						len += size;
1208						char *text = (char *)malloc(size);
1209						if (text == NULL) {
1210							puts("no memory!");
1211							return;
1212						}
1213						if (file.Read(text, size) < B_OK) {
1214							puts("could not read from file");
1215							free(text);
1216							continue;
1217						}
1218						if (!inserted) {
1219							GetSelection(&start, &end);
1220							Delete();
1221							inserted = true;
1222						}
1223
1224						int32 offset = 0;
1225						for (int32 loop = 0; loop < size; loop++) {
1226							if (text[loop] == '\n') {
1227								Insert(&text[offset], loop - offset + 1);
1228								offset = loop + 1;
1229							} else if (text[loop] == '\r') {
1230								text[loop] = '\n';
1231								Insert(&text[offset], loop - offset + 1);
1232								if ((loop + 1 < size)
1233										&& (text[loop + 1] == '\n'))
1234									loop++;
1235								offset = loop + 1;
1236							}
1237						}
1238						free(text);
1239					} else {
1240						isEnclosure = true;
1241						message.AddRef("refs", &ref);
1242					}
1243				}
1244			}
1245
1246			if (index == 1) {
1247				// message doesn't contain any refs - maybe the parent class likes it
1248				BTextView::MessageReceived(msg);
1249				break;
1250			}
1251
1252			if (inserted)
1253				Select(start, start + len);
1254			if (isEnclosure)
1255				Window()->PostMessage(&message, Window());
1256			break;
1257		}
1258
1259		case M_HEADER:
1260			msg->FindBool("header", &fHeader);
1261			SetText(NULL);
1262			LoadMessage(fMail, false, NULL);
1263			break;
1264
1265		case M_RAW:
1266			StopLoad();
1267
1268			msg->FindBool("raw", &fRaw);
1269			SetText(NULL);
1270			LoadMessage(fMail, false, NULL);
1271			break;
1272
1273		case M_SELECT:
1274			if (IsSelectable())
1275				Select(0, TextLength());
1276			break;
1277
1278		case M_SAVE:
1279			Save(msg);
1280			break;
1281
1282		case B_NODE_MONITOR:
1283		{
1284			int32 opcode;
1285			if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR) {
1286				dev_t device;
1287				if (msg->FindInt32("device", &device) < B_OK)
1288					break;
1289				ino_t inode;
1290				if (msg->FindInt64("node", &inode) < B_OK)
1291					break;
1292
1293				hyper_text *enclosure;
1294				for (int32 index = 0;
1295						(enclosure = (hyper_text *)fEnclosures->ItemAt(index++)) != NULL;) {
1296					if (device == enclosure->node.device
1297						&& inode == enclosure->node.node) {
1298						if (opcode == B_ENTRY_REMOVED) {
1299							enclosure->saved = false;
1300							enclosure->have_ref = false;
1301						} else if (opcode == B_ENTRY_MOVED) {
1302							enclosure->ref.device = device;
1303							msg->FindInt64("to directory", &enclosure->ref.directory);
1304
1305							const char *name;
1306							msg->FindString("name", &name);
1307							enclosure->ref.set_name(name);
1308						}
1309						break;
1310					}
1311				}
1312			}
1313			break;
1314		}
1315
1316		//
1317		// Tracker has responded to a BMessage that was dragged out of
1318		// this email message.  It has created a file for us, we just have to
1319		// put the stuff in it.
1320		//
1321		case B_COPY_TARGET:
1322		{
1323			BMessage data;
1324			if (msg->FindMessage("be:originator-data", &data) == B_OK) {
1325				entry_ref directory;
1326				const char *name;
1327				hyper_text *enclosure;
1328
1329				if (data.FindPointer("enclosure", (void **)&enclosure) == B_OK
1330					&& msg->FindString("name", &name) == B_OK
1331					&& msg->FindRef("directory", &directory) == B_OK) {
1332					switch (enclosure->type) {
1333						case TYPE_ENCLOSURE:
1334						case TYPE_BE_ENCLOSURE:
1335						{
1336							//
1337							//	Enclosure.  Decode the data and write it out.
1338							//
1339							BMessage saveMsg(M_SAVE);
1340							saveMsg.AddString("name", name);
1341							saveMsg.AddRef("directory", &directory);
1342							saveMsg.AddPointer("enclosure", enclosure);
1343							Save(&saveMsg, false);
1344							break;
1345						}
1346
1347						case TYPE_URL:
1348						{
1349							const char *replyType;
1350							if (msg->FindString("be:filetypes", &replyType) != B_OK)
1351								// drag recipient didn't ask for any specific type,
1352								// create a bookmark file as default
1353								replyType = "application/x-vnd.Be-bookmark";
1354
1355							BDirectory dir(&directory);
1356							BFile file(&dir, name, B_READ_WRITE);
1357							if (file.InitCheck() == B_OK) {
1358								if (strcmp(replyType, "application/x-vnd.Be-bookmark") == 0) {
1359									// we got a request to create a bookmark, stuff
1360									// it with the url attribute
1361									file.WriteAttr("META:url", B_STRING_TYPE, 0,
1362													enclosure->name, strlen(enclosure->name) + 1);
1363								} else if (strcasecmp(replyType, "text/plain") == 0) {
1364									// create a plain text file, stuff it with
1365									// the url as text
1366									file.Write(enclosure->name, strlen(enclosure->name));
1367								}
1368
1369								BNodeInfo fileInfo(&file);
1370								fileInfo.SetType(replyType);
1371							}
1372							break;
1373						}
1374
1375						case TYPE_MAILTO:
1376						{
1377							//
1378							//	Add some attributes to the already created
1379							//	person file.  Strip out the 'mailto:' if
1380							//  possible.
1381							//
1382							char *addrStart = enclosure->name;
1383							while (true) {
1384								if (*addrStart == ':') {
1385									addrStart++;
1386									break;
1387								}
1388
1389								if (*addrStart == '\0') {
1390									addrStart = enclosure->name;
1391									break;
1392								}
1393
1394								addrStart++;
1395							}
1396
1397							const char *replyType;
1398							if (msg->FindString("be:filetypes", &replyType) != B_OK)
1399								// drag recipient didn't ask for any specific type,
1400								// create a bookmark file as default
1401								replyType = "application/x-vnd.Be-bookmark";
1402
1403							BDirectory dir(&directory);
1404							BFile file(&dir, name, B_READ_WRITE);
1405							if (file.InitCheck() == B_OK) {
1406								if (!strcmp(replyType, "application/x-person")) {
1407									// we got a request to create a bookmark, stuff
1408									// it with the address attribute
1409									file.WriteAttr("META:email", B_STRING_TYPE, 0,
1410									  addrStart, strlen(enclosure->name) + 1);
1411								} else if (!strcasecmp(replyType, "text/plain")) {
1412									// create a plain text file, stuff it with the
1413									// email as text
1414									file.Write(addrStart, strlen(addrStart));
1415								}
1416
1417								BNodeInfo fileInfo(&file);
1418								fileInfo.SetType(replyType);
1419							}
1420							break;
1421						}
1422					}
1423				} else {
1424					//
1425					// Assume this is handled by BTextView...
1426					// (Probably drag clipping.)
1427					//
1428					BTextView::MessageReceived(msg);
1429				}
1430			}
1431			break;
1432		}
1433
1434		case B_INPUT_METHOD_EVENT:
1435		{
1436			int32 im_op;
1437			if (msg->FindInt32("be:opcode", &im_op) == B_OK){
1438				switch (im_op) {
1439					case B_INPUT_METHOD_STARTED:
1440						fInputMethodUndoState.replace = true;
1441						fInputMethodUndoState.active = true;
1442						break;
1443					case B_INPUT_METHOD_STOPPED:
1444						fInputMethodUndoState.active = false;
1445						if (fInputMethodUndoBuffer.CountItems() > 0) {
1446							KUndoItem *undo = fInputMethodUndoBuffer.ItemAt(
1447								fInputMethodUndoBuffer.CountItems() - 1);
1448							if (undo->History == K_INSERTED){
1449								fUndoBuffer.MakeNewUndoItem();
1450								fUndoBuffer.AddUndo(undo->RedoText, undo->Length,
1451									undo->Offset, undo->History, undo->CursorPos);
1452								fUndoBuffer.MakeNewUndoItem();
1453							}
1454							fInputMethodUndoBuffer.MakeEmpty();
1455						}
1456						break;
1457					case B_INPUT_METHOD_CHANGED:
1458						fInputMethodUndoState.active = true;
1459						break;
1460					case B_INPUT_METHOD_LOCATION_REQUEST:
1461						fInputMethodUndoState.active = true;
1462						break;
1463				}
1464			}
1465			BTextView::MessageReceived(msg);
1466			break;
1467		}
1468
1469		case M_REDO:
1470			Redo();
1471			break;
1472
1473		default:
1474			BTextView::MessageReceived(msg);
1475	}
1476}
1477
1478
1479void
1480TTextView::MouseDown(BPoint where)
1481{
1482	if (IsEditable()) {
1483		BPoint point;
1484		uint32 buttons;
1485		GetMouse(&point, &buttons);
1486		if (gDictCount && (buttons == B_SECONDARY_MOUSE_BUTTON)) {
1487			int32 offset, start, end, length;
1488			const char *text = Text();
1489			offset = OffsetAt(where);
1490			if (isalpha(text[offset])) {
1491				length = TextLength();
1492
1493				//Find start and end of word
1494				//FindSpellBoundry(length, offset, &start, &end);
1495
1496				char c;
1497				bool isAlpha, isApost, isCap;
1498				int32 first;
1499
1500				for (first = offset;
1501					(first >= 0) && (((c = text[first]) == '\'') || isalpha(c));
1502					first--) {}
1503				isCap = isupper(text[++first]);
1504
1505				for (start = offset, c = text[start], isAlpha = isalpha(c), isApost = (c=='\'');
1506					(start >= 0) && (isAlpha || (isApost
1507					&& (((c = text[start+1]) != 's') || !isCap) && isalpha(c)
1508					&& isalpha(text[start-1])));
1509					start--, c = text[start], isAlpha = isalpha(c), isApost = (c == '\'')) {}
1510				start++;
1511
1512				for (end = offset, c = text[end], isAlpha = isalpha(c), isApost = (c == '\'');
1513					(end < length) && (isAlpha || (isApost
1514					&& (((c = text[end + 1]) != 's') || !isCap) && isalpha(c)));
1515					end++, c = text[end], isAlpha = isalpha(c), isApost = (c == '\'')) {}
1516
1517				length = end - start;
1518				BString srcWord;
1519				srcWord.SetTo(text + start, length);
1520
1521				bool		foundWord = false;
1522				BList 		matches;
1523				BString 	*string;
1524
1525				BMenuItem *menuItem;
1526				BPopUpMenu menu("Words", false, false);
1527
1528				for (int32 i = 0; i < gDictCount; i++)
1529					gWords[i]->FindBestMatches(&matches,
1530						srcWord.String());
1531
1532				if (matches.CountItems()) {
1533					sort_word_list(&matches, srcWord.String());
1534					for (int32 i = 0; (string = (BString *)matches.ItemAt(i)) != NULL; i++) {
1535						menu.AddItem((menuItem = new BMenuItem(string->String(), NULL)));
1536						if (!strcasecmp(string->String(), srcWord.String())) {
1537							menuItem->SetEnabled(false);
1538							foundWord = true;
1539						}
1540						delete string;
1541					}
1542				} else {
1543					menuItem = new BMenuItem(B_TRANSLATE("No matches"), NULL);
1544					menuItem->SetEnabled(false);
1545					menu.AddItem(menuItem);
1546				}
1547
1548				BMenuItem *addItem = NULL;
1549				if (!foundWord && gUserDict >= 0) {
1550					menu.AddSeparatorItem();
1551					addItem = new BMenuItem(B_TRANSLATE("Add"), NULL);
1552					menu.AddItem(addItem);
1553				}
1554
1555				point = ConvertToScreen(where);
1556				if ((menuItem = menu.Go(point, false, false)) != NULL) {
1557					if (menuItem == addItem) {
1558						BString newItem(srcWord.String());
1559						newItem << "\n";
1560						gWords[gUserDict]->InitIndex();
1561						gExactWords[gUserDict]->InitIndex();
1562						gUserDictFile->Write(newItem.String(), newItem.Length());
1563						gWords[gUserDict]->BuildIndex();
1564						gExactWords[gUserDict]->BuildIndex();
1565
1566						if (fSpellCheck)
1567							CheckSpelling(0, TextLength());
1568					} else {
1569						int32 len = strlen(menuItem->Label());
1570						Select(start, start);
1571						Delete(start, end);
1572						Insert(start, menuItem->Label(), len);
1573						Select(start+len, start+len);
1574					}
1575				}
1576			}
1577			return;
1578		} else if (fSpellCheck && IsEditable()) {
1579			int32 start, end;
1580
1581			GetSelection(&start, &end);
1582			FindSpellBoundry(1, start, &start, &end);
1583			CheckSpelling(start, end);
1584		}
1585	} else {
1586		// is not editable, look for enclosures/links
1587
1588		int32 clickOffset = OffsetAt(where);
1589		int32 items = fEnclosures->CountItems();
1590		for (int32 loop = 0; loop < items; loop++) {
1591			hyper_text *enclosure = (hyper_text*) fEnclosures->ItemAt(loop);
1592			if (clickOffset < enclosure->text_start || clickOffset >= enclosure->text_end)
1593				continue;
1594
1595			//
1596			// The user is clicking on this attachment
1597			//
1598
1599			int32 start;
1600			int32 finish;
1601			Select(enclosure->text_start, enclosure->text_end);
1602			GetSelection(&start, &finish);
1603			Window()->UpdateIfNeeded();
1604
1605			bool drag = false;
1606			bool held = false;
1607			uint32 buttons = 0;
1608			if (Window()->CurrentMessage()) {
1609				Window()->CurrentMessage()->FindInt32("buttons",
1610				  (int32 *) &buttons);
1611			}
1612
1613			//
1614			// If this is the primary button, wait to see if the user is going
1615			// to single click, hold, or drag.
1616			//
1617			if (buttons != B_SECONDARY_MOUSE_BUTTON) {
1618				BPoint point = where;
1619				bigtime_t popupDelay;
1620				get_click_speed(&popupDelay);
1621				popupDelay *= 2;
1622				popupDelay += system_time();
1623				while (buttons && abs((int)(point.x - where.x)) < 4
1624					&& abs((int)(point.y - where.y)) < 4
1625					&& system_time() < popupDelay) {
1626					snooze(10000);
1627					GetMouse(&point, &buttons);
1628				}
1629
1630				if (system_time() < popupDelay) {
1631					//
1632					// The user either dragged this or released the button.
1633					// check if it was dragged.
1634					//
1635					if (!(abs((int)(point.x - where.x)) < 4
1636						&& abs((int)(point.y - where.y)) < 4) && buttons)
1637						drag = true;
1638				} else  {
1639					//
1640					//	The user held the button down.
1641					//
1642					held = true;
1643				}
1644			}
1645
1646			//
1647			//	If the user has right clicked on this menu,
1648			// 	or held the button down on it for a while,
1649			//	pop up a context menu.
1650			//
1651			if (buttons == B_SECONDARY_MOUSE_BUTTON || held) {
1652				//
1653				// Right mouse click... Display a menu
1654				//
1655				BPoint point = where;
1656				ConvertToScreen(&point);
1657
1658				BMenuItem *item;
1659				if ((enclosure->type != TYPE_ENCLOSURE)
1660					&& (enclosure->type != TYPE_BE_ENCLOSURE))
1661					item = fLinkMenu->Go(point, true);
1662				else
1663					item = fEnclosureMenu->Go(point, true);
1664
1665				BMessage *msg;
1666				if (item && (msg = item->Message()) != NULL) {
1667					if (msg->what == M_SAVE) {
1668						if (fPanel)
1669							fPanel->SetEnclosure(enclosure);
1670						else {
1671							fPanel = new TSavePanel(enclosure, this);
1672							fPanel->Window()->Show();
1673						}
1674					} else if (msg->what == M_COPY) {
1675						// copy link location to clipboard
1676
1677						if (be_clipboard->Lock()) {
1678							be_clipboard->Clear();
1679
1680							BMessage *clip;
1681							if ((clip = be_clipboard->Data()) != NULL) {
1682								clip->AddData("text/plain", B_MIME_TYPE,
1683									enclosure->name, strlen(enclosure->name));
1684								be_clipboard->Commit();
1685							}
1686							be_clipboard->Unlock();
1687						}
1688					} else
1689						Open(enclosure);
1690				}
1691			} else {
1692				//
1693				// Left button.  If the user single clicks, open this link.
1694				// Otherwise, initiate a drag.
1695				//
1696				if (drag) {
1697					BMessage dragMessage(B_SIMPLE_DATA);
1698					dragMessage.AddInt32("be:actions", B_COPY_TARGET);
1699					dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1700					switch (enclosure->type) {
1701						case TYPE_BE_ENCLOSURE:
1702						case TYPE_ENCLOSURE:
1703							//
1704							// Attachment.  The type is specified in the message.
1705							//
1706							dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1707							dragMessage.AddString("be:filetypes",
1708								enclosure->content_type ? enclosure->content_type : "");
1709							dragMessage.AddString("be:clip_name", enclosure->name);
1710							break;
1711
1712						case TYPE_URL:
1713							//
1714							// URL.  The user can drag it into the tracker to
1715							// create a bookmark file.
1716							//
1717							dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1718							dragMessage.AddString("be:filetypes",
1719							  "application/x-vnd.Be-bookmark");
1720							dragMessage.AddString("be:filetypes", "text/plain");
1721							dragMessage.AddString("be:clip_name", "Bookmark");
1722
1723							dragMessage.AddString("be:url", enclosure->name);
1724							break;
1725
1726						case TYPE_MAILTO:
1727							//
1728							// Mailto address.  The user can drag it into the
1729							// tracker to create a people file.
1730							//
1731							dragMessage.AddString("be:types", B_FILE_MIME_TYPE);
1732							dragMessage.AddString("be:filetypes",
1733							  "application/x-person");
1734							dragMessage.AddString("be:filetypes", "text/plain");
1735							dragMessage.AddString("be:clip_name", "Person");
1736
1737							dragMessage.AddString("be:email", enclosure->name);
1738							break;
1739
1740						default:
1741							//
1742							// Otherwise it doesn't have a type that I know how
1743							// to save.  It won't have any types and if any
1744							// program wants to accept it, more power to them.
1745							// (tracker won't.)
1746							//
1747							dragMessage.AddString("be:clip_name", "Hyperlink");
1748					}
1749
1750					BMessage data;
1751					data.AddPointer("enclosure", enclosure);
1752					dragMessage.AddMessage("be:originator-data", &data);
1753
1754					BRegion selectRegion;
1755					GetTextRegion(start, finish, &selectRegion);
1756					DragMessage(&dragMessage, selectRegion.Frame(), this);
1757				} else {
1758					//
1759					//	User Single clicked on the attachment.  Open it.
1760					//
1761					Open(enclosure);
1762				}
1763			}
1764			return;
1765		}
1766	}
1767	BTextView::MouseDown(where);
1768}
1769
1770
1771void
1772TTextView::MouseMoved(BPoint where, uint32 code, const BMessage *msg)
1773{
1774	int32 start = OffsetAt(where);
1775
1776	for (int32 loop = fEnclosures->CountItems(); loop-- > 0;) {
1777		hyper_text *enclosure = (hyper_text *)fEnclosures->ItemAt(loop);
1778		if ((start >= enclosure->text_start) && (start < enclosure->text_end)) {
1779			if (!fCursor)
1780				SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
1781			fCursor = true;
1782			return;
1783		}
1784	}
1785
1786	if (fCursor) {
1787		SetViewCursor(B_CURSOR_I_BEAM);
1788		fCursor = false;
1789	}
1790
1791	BTextView::MouseMoved(where, code, msg);
1792}
1793
1794
1795void
1796TTextView::ClearList()
1797{
1798	hyper_text *enclosure;
1799	while ((enclosure = (hyper_text *)fEnclosures->FirstItem()) != NULL) {
1800		fEnclosures->RemoveItem(enclosure);
1801
1802		if (enclosure->name)
1803			free(enclosure->name);
1804		if (enclosure->content_type)
1805			free(enclosure->content_type);
1806		if (enclosure->encoding)
1807			free(enclosure->encoding);
1808		if (enclosure->have_ref && !enclosure->saved) {
1809			BEntry entry(&enclosure->ref);
1810			entry.Remove();
1811		}
1812
1813		watch_node(&enclosure->node, B_STOP_WATCHING, this);
1814		free(enclosure);
1815	}
1816}
1817
1818
1819void
1820TTextView::LoadMessage(BEmailMessage *mail, bool quoteIt, const char *text)
1821{
1822	StopLoad();
1823
1824	fMail = mail;
1825
1826	ClearList();
1827
1828	MakeSelectable(true);
1829	MakeEditable(false);
1830	if (text)
1831		Insert(text, strlen(text));
1832
1833	//attr_info attrInfo;
1834	TTextView::Reader *reader = new TTextView::Reader(fHeader, fRaw, quoteIt, fIncoming,
1835				text != NULL, true,
1836				// I removed the following, because I absolutely can't imagine why it's
1837				// there (the mail kit should be able to deal with non-compliant mails)
1838				// -- axeld.
1839				// fFile->GetAttrInfo(B_MAIL_ATTR_MIME, &attrInfo) == B_OK,
1840				this, mail, fEnclosures, fStopSem);
1841
1842	resume_thread(fThread = spawn_thread(Reader::Run, "reader", B_NORMAL_PRIORITY, reader));
1843}
1844
1845
1846void
1847TTextView::Open(hyper_text *enclosure)
1848{
1849	switch (enclosure->type) {
1850		case TYPE_URL:
1851		{
1852			const struct {const char *urlType, *handler; } handlerTable[] = {
1853				{"http",	B_URL_HTTP},
1854				{"https",	B_URL_HTTPS},
1855				{"ftp",		B_URL_FTP},
1856				{"gopher",	B_URL_GOPHER},
1857				{"mailto",	B_URL_MAILTO},
1858				{"news",	B_URL_NEWS},
1859				{"nntp",	B_URL_NNTP},
1860				{"telnet",	B_URL_TELNET},
1861				{"rlogin",	B_URL_RLOGIN},
1862				{"tn3270",	B_URL_TN3270},
1863				{"wais",	B_URL_WAIS},
1864				{"file",	B_URL_FILE},
1865				{NULL,		NULL}
1866			};
1867			const char *handlerToLaunch = NULL;
1868
1869			const char *colonPos = strchr(enclosure->name, ':');
1870			if (colonPos) {
1871				int urlTypeLength = colonPos - enclosure->name;
1872
1873				for (int32 index = 0; handlerTable[index].urlType; index++) {
1874					if (!strncasecmp(enclosure->name,
1875							handlerTable[index].urlType, urlTypeLength)) {
1876						handlerToLaunch = handlerTable[index].handler;
1877						break;
1878					}
1879				}
1880			}
1881			if (handlerToLaunch) {
1882				entry_ref appRef;
1883				if (be_roster->FindApp(handlerToLaunch, &appRef) != B_OK)
1884					handlerToLaunch = NULL;
1885			}
1886			if (!handlerToLaunch)
1887				handlerToLaunch = "application/x-vnd.Be-Bookmark";
1888
1889			status_t result = be_roster->Launch(handlerToLaunch, 1, &enclosure->name);
1890			if (result != B_NO_ERROR && result != B_ALREADY_RUNNING) {
1891				beep();
1892				BAlert* alert = new BAlert("",
1893					B_TRANSLATE("There is no installed handler for "
1894						"URL links."), B_TRANSLATE("Sorry"));
1895				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1896				alert->Go();
1897			}
1898			break;
1899		}
1900
1901		case TYPE_MAILTO:
1902			if (be_roster->Launch(B_MAIL_TYPE, 1, &enclosure->name) < B_OK) {
1903				char *argv[] = {(char *)"Mail", enclosure->name};
1904				be_app->ArgvReceived(2, argv);
1905			}
1906			break;
1907
1908		case TYPE_ENCLOSURE:
1909		case TYPE_BE_ENCLOSURE:
1910			if (!enclosure->have_ref) {
1911				BPath path;
1912				if (find_directory(B_COMMON_TEMP_DIRECTORY, &path) == B_NO_ERROR) {
1913					BDirectory dir(path.Path());
1914					if (dir.InitCheck() == B_NO_ERROR) {
1915						char name[B_FILE_NAME_LENGTH];
1916						char baseName[B_FILE_NAME_LENGTH];
1917						strcpy(baseName, enclosure->name ? enclosure->name : "enclosure");
1918						strcpy(name, baseName);
1919						for (int32 index = 0; dir.Contains(name); index++)
1920							sprintf(name, "%s_%ld", baseName, index);
1921
1922						BEntry entry(path.Path());
1923						entry_ref ref;
1924						entry.GetRef(&ref);
1925
1926						BMessage save(M_SAVE);
1927						save.AddRef("directory", &ref);
1928						save.AddString("name", name);
1929						save.AddPointer("enclosure", enclosure);
1930						if (Save(&save) != B_NO_ERROR)
1931							break;
1932						enclosure->saved = false;
1933					}
1934				}
1935			}
1936
1937			BMessenger tracker("application/x-vnd.Be-TRAK");
1938			if (tracker.IsValid()) {
1939				BMessage openMsg(B_REFS_RECEIVED);
1940				openMsg.AddRef("refs", &enclosure->ref);
1941				tracker.SendMessage(&openMsg);
1942			}
1943			break;
1944	}
1945}
1946
1947
1948status_t
1949TTextView::Save(BMessage *msg, bool makeNewFile)
1950{
1951	const char		*name;
1952	entry_ref		ref;
1953	BFile			file;
1954	BPath			path;
1955	hyper_text		*enclosure;
1956	status_t		result = B_NO_ERROR;
1957	char 			entry_name[B_FILE_NAME_LENGTH];
1958
1959	msg->FindString("name", &name);
1960	msg->FindRef("directory", &ref);
1961	msg->FindPointer("enclosure", (void **)&enclosure);
1962
1963	BDirectory dir;
1964	dir.SetTo(&ref);
1965	result = dir.InitCheck();
1966
1967	if (result == B_OK) {
1968		if (makeNewFile) {
1969			//
1970			// Search for the file and delete it if it already exists.
1971			// (It may not, that's ok.)
1972			//
1973			BEntry entry;
1974			if (dir.FindEntry(name, &entry) == B_NO_ERROR)
1975				entry.Remove();
1976
1977			if ((enclosure->have_ref) && (!enclosure->saved)) {
1978				entry.SetTo(&enclosure->ref);
1979
1980				//
1981				// Added true arg and entry_name so MoveTo clobbers as
1982				// before. This may not be the correct behaviour, but
1983				// it's the preserved behaviour.
1984				//
1985				entry.GetName(entry_name);
1986				result = entry.MoveTo(&dir, entry_name, true);
1987				if (result == B_NO_ERROR) {
1988					entry.Rename(name);
1989					entry.GetRef(&enclosure->ref);
1990					entry.GetNodeRef(&enclosure->node);
1991					enclosure->saved = true;
1992					return result;
1993				}
1994			}
1995
1996			if (result == B_NO_ERROR) {
1997				result = dir.CreateFile(name, &file);
1998				if (result == B_NO_ERROR && enclosure->content_type) {
1999					char type[B_MIME_TYPE_LENGTH];
2000
2001					if (!strcasecmp(enclosure->content_type, "message/rfc822"))
2002						strcpy(type, "text/x-email");
2003					else if (!strcasecmp(enclosure->content_type, "message/delivery-status"))
2004						strcpy(type, "text/plain");
2005					else
2006						strcpy(type, enclosure->content_type);
2007
2008					BNodeInfo info(&file);
2009					info.SetType(type);
2010				}
2011			}
2012		} else {
2013			//
2014			// 	This file was dragged into the tracker or desktop.  The file
2015			//	already exists.
2016			//
2017			result = file.SetTo(&dir, name, B_WRITE_ONLY);
2018		}
2019	}
2020
2021	if (enclosure->component == NULL)
2022		result = B_ERROR;
2023
2024	if (result == B_NO_ERROR) {
2025		//
2026		// Write the data
2027		//
2028		enclosure->component->GetDecodedData(&file);
2029
2030		BEntry entry;
2031		dir.FindEntry(name, &entry);
2032		entry.GetRef(&enclosure->ref);
2033		enclosure->have_ref = true;
2034		enclosure->saved = true;
2035		entry.GetPath(&path);
2036		update_mime_info(path.Path(), false, true,
2037			!cistrcmp("application/octet-stream", enclosure->content_type ? enclosure->content_type : B_EMPTY_STRING));
2038		entry.GetNodeRef(&enclosure->node);
2039		watch_node(&enclosure->node, B_WATCH_NAME, this);
2040	}
2041
2042	if (result != B_NO_ERROR) {
2043		beep();
2044		BAlert* alert = new BAlert("", B_TRANSLATE("An error occurred trying to save "
2045				"the attachment."),	B_TRANSLATE("Sorry"));
2046		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2047		alert->Go();
2048	}
2049
2050	return result;
2051}
2052
2053
2054void
2055TTextView::StopLoad()
2056{
2057	Window()->Unlock();
2058
2059	thread_info	info;
2060	if (fThread != 0 && get_thread_info(fThread, &info) == B_NO_ERROR) {
2061		fStopLoading = true;
2062		acquire_sem(fStopSem);
2063		int32 result;
2064		wait_for_thread(fThread, &result);
2065		fThread = 0;
2066		release_sem(fStopSem);
2067		fStopLoading = false;
2068	}
2069
2070	Window()->Lock();
2071}
2072
2073
2074bool
2075TTextView::IsReaderThreadRunning()
2076{
2077	if (fThread == 0)
2078		return false;
2079
2080	thread_info info;
2081	for (int i = 5; i > 0; i--, usleep(100000))
2082		if (get_thread_info(fThread, &info) != B_OK)
2083			return false;
2084	return true;
2085}
2086
2087
2088void
2089TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_encoding encoding)
2090{
2091	if (mail == NULL)
2092		return;
2093
2094	int32 textLength = TextLength();
2095	const char *text = Text();
2096
2097	BTextMailComponent *body = mail->Body();
2098	if (body == NULL) {
2099		if (mail->SetBody(body = new BTextMailComponent()) < B_OK)
2100			return;
2101	}
2102	body->SetEncoding(encoding, charset);
2103
2104	// Just add the text as a whole if we can, or ...
2105	if (!wrap) {
2106		body->AppendText(text);
2107		return;
2108	}
2109
2110	// ... do word wrapping.
2111
2112	BWindow	*window = Window();
2113	char *saveText = strdup(text);
2114	BRect saveTextRect = TextRect();
2115
2116	// do this before we start messing with the fonts
2117	// the user will never know...
2118	window->DisableUpdates();
2119	Hide();
2120	BScrollBar *vScroller = ScrollBar(B_VERTICAL);
2121	BScrollBar *hScroller = ScrollBar(B_HORIZONTAL);
2122	if (vScroller != NULL)
2123		vScroller->SetTarget((BView *)NULL);
2124	if (hScroller != NULL)
2125		hScroller->SetTarget((BView *)NULL);
2126
2127	// Temporarily set the font to a fixed width font for line wrapping
2128	// calculations.  If the font doesn't have as many of the symbols as
2129	// the preferred font, go back to using the user's preferred font.
2130
2131	bool *boolArray;
2132	int missingCharactersFixedWidth = 0;
2133	int missingCharactersPreferredFont = 0;
2134	int32 numberOfCharacters;
2135
2136	numberOfCharacters = BString(text).CountChars();
2137	if (numberOfCharacters > 0
2138		&& (boolArray = (bool *)malloc(sizeof(bool) * numberOfCharacters)) != NULL) {
2139		memset(boolArray, 0, sizeof (bool) * numberOfCharacters);
2140		be_fixed_font->GetHasGlyphs(text, numberOfCharacters, boolArray);
2141		for (int i = 0; i < numberOfCharacters; i++) {
2142			if (!boolArray[i])
2143				missingCharactersFixedWidth += 1;
2144		}
2145
2146		memset(boolArray, 0, sizeof (bool) * numberOfCharacters);
2147		fFont.GetHasGlyphs(text, numberOfCharacters, boolArray);
2148		for (int i = 0; i < numberOfCharacters; i++) {
2149			if (!boolArray[i])
2150				missingCharactersPreferredFont += 1;
2151		}
2152
2153		free(boolArray);
2154	}
2155
2156	if (missingCharactersFixedWidth > missingCharactersPreferredFont)
2157		SetFontAndColor(0, textLength, &fFont);
2158	else // All things being equal, the fixed font is better for wrapping.
2159		SetFontAndColor(0, textLength, be_fixed_font);
2160
2161	// calculate a text rect that is 72 columns wide
2162	BRect newTextRect = saveTextRect;
2163	newTextRect.right = newTextRect.left + be_fixed_font->StringWidth("m") * 72;
2164	SetTextRect(newTextRect);
2165
2166	// hard-wrap, based on TextView's soft-wrapping
2167	int32 numLines = CountLines();
2168	bool spaceMoved = false;
2169	char *content = (char *)malloc(textLength + numLines * 72);	// more we'll ever need
2170	if (content != NULL) {
2171		int32 contentLength = 0;
2172
2173		for (int32 i = 0; i < numLines; i++) {
2174			int32 startOffset = OffsetAt(i);
2175			if (spaceMoved) {
2176				startOffset++;
2177				spaceMoved = false;
2178			}
2179			int32 endOffset = OffsetAt(i + 1);
2180			int32 lineLength = endOffset - startOffset;
2181
2182			// quick hack to not break URLs into several parts
2183			for (int32 pos = startOffset; pos < endOffset; pos++) {
2184				size_t urlLength;
2185				uint8 type = CheckForURL(text + pos, urlLength);
2186				if (type != 0)
2187					pos += urlLength;
2188
2189				if (pos > endOffset) {
2190					// find first break character after the URL
2191					for (; text[pos]; pos++) {
2192						if (isalnum(text[pos]) || isspace(text[pos]))
2193							break;
2194					}
2195					if (text[pos] && isspace(text[pos]) && text[pos] != '\n')
2196						pos++;
2197
2198					endOffset += pos - endOffset;
2199					lineLength = endOffset - startOffset;
2200
2201					// insert a newline (and the same number of quotes) after the
2202					// URL to make sure the rest of the text is properly wrapped
2203
2204					char buffer[64];
2205					if (text[pos] == '\n')
2206						buffer[0] = '\0';
2207					else
2208						strcpy(buffer, "\n");
2209
2210					size_t quoteLength;
2211					CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength);
2212
2213					Insert(pos, buffer, strlen(buffer));
2214					numLines = CountLines();
2215					text = Text();
2216					i++;
2217				}
2218			}
2219			if (text[endOffset - 1] != ' '
2220				&& text[endOffset - 1] != '\n'
2221				&& text[endOffset] == ' ') {
2222				// make sure spaces will be part of this line
2223				endOffset++;
2224				lineLength++;
2225				spaceMoved = true;
2226			}
2227
2228			memcpy(content + contentLength, text + startOffset, lineLength);
2229			contentLength += lineLength;
2230
2231			// add a newline to every line except for the ones
2232			// that already end in newlines, and the last line
2233			if ((text[endOffset - 1] != '\n') && (i < numLines - 1)) {
2234				content[contentLength++] = '\n';
2235
2236				// copy quote level of the first line
2237				size_t quoteLength;
2238				CopyQuotes(text + startOffset, lineLength, content + contentLength, quoteLength);
2239				contentLength += quoteLength;
2240			}
2241		}
2242		content[contentLength] = '\0';
2243
2244		body->AppendText(content);
2245		free(content);
2246	}
2247
2248	// reset the text rect and font
2249	SetTextRect(saveTextRect);
2250	SetText(saveText);
2251	free(saveText);
2252	SetFontAndColor(0, textLength, &fFont);
2253
2254	// should be OK to hook these back up now
2255	if (vScroller != NULL)
2256		vScroller->SetTarget(this);
2257	if (hScroller != NULL)
2258		hScroller->SetTarget(this);
2259
2260	Show();
2261	window->EnableUpdates();
2262}
2263
2264
2265//	#pragma mark -
2266
2267
2268TTextView::Reader::Reader(bool header, bool raw, bool quote, bool incoming,
2269		bool stripHeader, bool mime, TTextView *view, BEmailMessage *mail,
2270		BList *list, sem_id sem)
2271	:
2272	fHeader(header),
2273	fRaw(raw),
2274	fQuote(quote),
2275	fIncoming(incoming),
2276	fStripHeader(stripHeader),
2277	fMime(mime),
2278	fView(view),
2279	fMail(mail),
2280	fEnclosures(list),
2281	fStopSem(sem)
2282{
2283}
2284
2285
2286bool
2287TTextView::Reader::ParseMail(BMailContainer *container,
2288	BTextMailComponent *ignore)
2289{
2290	int32 count = 0;
2291	for (int32 i = 0; i < container->CountComponents(); i++) {
2292		if (fView->fStopLoading)
2293			return false;
2294
2295		BMailComponent *component;
2296		if ((component = container->GetComponent(i)) == NULL) {
2297			if (fView->fStopLoading)
2298				return false;
2299
2300			hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
2301			if (enclosure == NULL)
2302				return false;
2303
2304			memset(enclosure, 0, sizeof(hyper_text));
2305
2306			enclosure->type = TYPE_ENCLOSURE;
2307
2308			const char *name = "\n<Attachment: could not handle>\n";
2309
2310			fView->GetSelection(&enclosure->text_start, &enclosure->text_end);
2311			enclosure->text_start++;
2312			enclosure->text_end += strlen(name) - 1;
2313
2314			Insert(name, strlen(name), true);
2315			fEnclosures->AddItem(enclosure);
2316			continue;
2317		}
2318
2319		count++;
2320		if (component == ignore)
2321			continue;
2322
2323		if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) {
2324			BMIMEMultipartMailContainer *c = dynamic_cast<BMIMEMultipartMailContainer *>(container->GetComponent(i));
2325			ASSERT(c != NULL);
2326
2327			if (!ParseMail(c, ignore))
2328				count--;
2329		} else if (fIncoming) {
2330			hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
2331			if (enclosure == NULL)
2332				return false;
2333
2334			memset(enclosure, 0, sizeof(hyper_text));
2335
2336			enclosure->type = TYPE_ENCLOSURE;
2337			enclosure->component = component;
2338
2339			BString name;
2340			char fileName[B_FILE_NAME_LENGTH];
2341			strcpy(fileName, "untitled");
2342			if (BMailAttachment *attachment = dynamic_cast <BMailAttachment *> (component))
2343				attachment->FileName(fileName);
2344
2345			BPath path(fileName);
2346			enclosure->name = strdup(path.Leaf());
2347
2348			BMimeType type;
2349			component->MIMEType(&type);
2350			enclosure->content_type = strdup(type.Type());
2351
2352			char typeDescription[B_MIME_TYPE_LENGTH];
2353			if (type.GetShortDescription(typeDescription) != B_OK)
2354				strcpy(typeDescription, type.Type() ? type.Type() : B_EMPTY_STRING);
2355
2356			name = "\n<";
2357			name.Append(B_TRANSLATE_COMMENT("Enclosure: %name% (Type: %type%)",
2358				"Don't translate the variables %name% and %type%."));
2359			name.Append(">\n");
2360			name.ReplaceFirst("%name%", enclosure->name);
2361			name.ReplaceFirst("%type%", typeDescription);
2362
2363			fView->GetSelection(&enclosure->text_start, &enclosure->text_end);
2364			enclosure->text_start++;
2365			enclosure->text_end += strlen(name.String()) - 1;
2366
2367			Insert(name.String(), name.Length(), true);
2368			fEnclosures->AddItem(enclosure);
2369		}
2370//			default:
2371//			{
2372//				PlainTextBodyComponent *body = dynamic_cast<PlainTextBodyComponent *>(container->GetComponent(i));
2373//				const char *text;
2374//				if (body && (text = body->Text()) != NULL)
2375//					Insert(text, strlen(text), false);
2376//			}
2377	}
2378	return count > 0;
2379}
2380
2381
2382bool
2383TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader)
2384{
2385	char line[522];
2386	int32 count = 0;
2387
2388	for (int32 loop = 0; loop < data_len; loop++) {
2389		if (fView->fStopLoading)
2390			return false;
2391
2392		if (fQuote && (!loop || (loop && data[loop - 1] == '\n'))) {
2393			strcpy(&line[count], QUOTE);
2394			count += strlen(QUOTE);
2395		}
2396		if (!fRaw && fIncoming && (loop < data_len - 7)) {
2397			size_t urlLength;
2398			BString url;
2399			uint8 type = CheckForURL(data + loop, urlLength, &url);
2400
2401			if (type) {
2402				if (!Insert(line, count, false, isHeader))
2403					return false;
2404				count = 0;
2405
2406				hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text));
2407				if (enclosure == NULL)
2408					return false;
2409
2410				memset(enclosure, 0, sizeof(hyper_text));
2411				fView->GetSelection(&enclosure->text_start,
2412									&enclosure->text_end);
2413				enclosure->type = type;
2414				enclosure->name = strdup(url.String());
2415				if (enclosure->name == NULL) {
2416					free(enclosure);
2417					return false;
2418				}
2419
2420				Insert(&data[loop], urlLength, true, isHeader);
2421				enclosure->text_end += urlLength;
2422				loop += urlLength - 1;
2423
2424				fEnclosures->AddItem(enclosure);
2425				continue;
2426			}
2427		}
2428		if (!fRaw && fMime && data[loop] == '=') {
2429			if ((loop) && (loop < data_len - 1) && (data[loop + 1] == '\r'))
2430				loop += 2;
2431			else
2432				line[count++] = data[loop];
2433		} else if (data[loop] != '\r')
2434			line[count++] = data[loop];
2435
2436		if (count > 511 || (count && loop == data_len - 1)) {
2437			if (!Insert(line, count, false, isHeader))
2438				return false;
2439			count = 0;
2440		}
2441	}
2442	return true;
2443}
2444
2445
2446bool
2447TTextView::Reader::Insert(const char *line, int32 count, bool isHyperLink,
2448	bool isHeader)
2449{
2450	if (!count)
2451		return true;
2452
2453	BFont font(fView->Font());
2454	TextRunArray style(count / 8 + 8);
2455
2456	if (fView->fColoredQuotes && !isHeader && !isHyperLink) {
2457		FillInQuoteTextRuns(fView, &fQuoteContext, line, count, font,
2458			&style.Array(), style.MaxEntries());
2459	} else {
2460		text_run_array &array = style.Array();
2461		array.count = 1;
2462		array.runs[0].offset = 0;
2463		if (isHeader) {
2464			array.runs[0].color = isHyperLink ? kHyperLinkColor : kHeaderColor;
2465			font.SetSize(font.Size() * 0.9);
2466		} else {
2467			array.runs[0].color = isHyperLink
2468				? kHyperLinkColor : kNormalTextColor;
2469		}
2470		array.runs[0].font = font;
2471	}
2472
2473	if (!fView->Window()->Lock())
2474		return false;
2475
2476	fView->Insert(fView->TextLength(), line, count, &style.Array());
2477
2478	fView->Window()->Unlock();
2479	return true;
2480}
2481
2482
2483status_t
2484TTextView::Reader::Run(void *_this)
2485{
2486	Reader *reader = (Reader *)_this;
2487	TTextView *view = reader->fView;
2488	char *msg = NULL;
2489	off_t size = 0;
2490	int32 len = 0;
2491
2492	if (!reader->Lock())
2493		return B_INTERRUPTED;
2494
2495	BFile *file = dynamic_cast<BFile *>(reader->fMail->Data());
2496	if (file != NULL) {
2497		len = header_len(file);
2498
2499		if (reader->fHeader)
2500			size = len;
2501		if (reader->fRaw || !reader->fMime)
2502			file->GetSize(&size);
2503
2504		if (size != 0 && (msg = (char *)malloc(size)) == NULL)
2505			goto done;
2506		file->Seek(0, 0);
2507
2508		if (msg)
2509			size = file->Read(msg, size);
2510	}
2511
2512	// show the header?
2513	if (reader->fHeader && len) {
2514		// strip all headers except "From", "To", "Reply-To", "Subject", and "Date"
2515	 	if (reader->fStripHeader) {
2516		 	const char *header = msg;
2517		 	char *buffer = NULL;
2518
2519			while (strncmp(header, "\r\n", 2)) {
2520				const char *eol = header;
2521				while ((eol = strstr(eol, "\r\n")) != NULL && isspace(eol[2]))
2522					eol += 2;
2523				if (eol == NULL)
2524					break;
2525
2526				eol += 2;	// CR+LF belong to the line
2527				size_t length = eol - header;
2528
2529		 		buffer = (char *)realloc(buffer, length + 1);
2530		 		if (buffer == NULL)
2531		 			goto done;
2532
2533		 		memcpy(buffer, header, length);
2534
2535				length = rfc2047_to_utf8(&buffer, &length, length);
2536
2537		 		if (!strncasecmp(header, "Reply-To: ", 10)
2538		 			|| !strncasecmp(header, "To: ", 4)
2539		 			|| !strncasecmp(header, "From: ", 6)
2540		 			|| !strncasecmp(header, "Subject: ", 8)
2541		 			|| !strncasecmp(header, "Date: ", 6))
2542		 			reader->Process(buffer, length, true);
2543
2544		 		header = eol;
2545	 		}
2546		 	free(buffer);
2547	 		reader->Process("\r\n", 2, true);
2548	 	}
2549	 	else if (!reader->Process(msg, len, true))
2550			goto done;
2551	}
2552
2553	if (reader->fRaw) {
2554		if (!reader->Process((const char *)msg + len, size - len))
2555			goto done;
2556	} else {
2557		//reader->fFile->Seek(0, 0);
2558		//BEmailMessage *mail = new BEmailMessage(reader->fFile);
2559		BEmailMessage *mail = reader->fMail;
2560
2561		// at first, insert the mail body
2562		BTextMailComponent *body = NULL;
2563		if (mail->BodyText() && !view->fStopLoading) {
2564			char *bodyText = const_cast<char *>(mail->BodyText());
2565			int32 bodyLength = strlen(bodyText);
2566			body = mail->Body();
2567			bool isHTML = false;
2568
2569			BMimeType type;
2570			if (body->MIMEType(&type) == B_OK && type == "text/html") {
2571				// strip out HTML tags
2572				char *t = bodyText, *a, *end = bodyText + bodyLength;
2573				bodyText = (char *)malloc(bodyLength + 1);
2574				isHTML = true;
2575
2576				// TODO: is it correct to assume that the text is in Latin-1?
2577				//		because if it isn't, the code below won't work correctly...
2578
2579				for (a = bodyText; t < end; t++) {
2580					int32 c = *t;
2581
2582					// compact spaces
2583					bool space = false;
2584					while (c && (c == ' ' || c == '\t')) {
2585						c = *(++t);
2586						space = true;
2587					}
2588					if (space) {
2589						c = ' ';
2590						t--;
2591					} else if (FilterHTMLTag(c, &t, end))	// the tag filter
2592						continue;
2593
2594					Unicode2UTF8(c, &a);
2595				}
2596
2597				*a = 0;
2598				bodyLength = strlen(bodyText);
2599				body = NULL;	// to add the HTML text as enclosure
2600			}
2601			if (!reader->Process(bodyText, bodyLength))
2602				goto done;
2603
2604			if (isHTML)
2605				free(bodyText);
2606		}
2607
2608		if (!reader->ParseMail(mail, body))
2609			goto done;
2610
2611		//reader->fView->fMail = mail;
2612	}
2613
2614	if (!view->fStopLoading && view->Window()->Lock()) {
2615		view->Select(0, 0);
2616		view->MakeSelectable(true);
2617		if (!reader->fIncoming)
2618			view->MakeEditable(true);
2619
2620		view->Window()->Unlock();
2621	}
2622
2623done:
2624	// restore the reading position if available
2625	view->Window()->PostMessage(M_READ_POS);
2626
2627	reader->Unlock();
2628
2629	delete reader;
2630	free(msg);
2631
2632	return B_NO_ERROR;
2633}
2634
2635
2636status_t
2637TTextView::Reader::Unlock()
2638{
2639	return release_sem(fStopSem);
2640}
2641
2642
2643bool
2644TTextView::Reader::Lock()
2645{
2646	if (acquire_sem_etc(fStopSem, 1, B_TIMEOUT, 0) != B_NO_ERROR)
2647		return false;
2648
2649	return true;
2650}
2651
2652
2653//====================================================================
2654//	#pragma mark -
2655
2656
2657TSavePanel::TSavePanel(hyper_text *enclosure, TTextView *view)
2658	: BFilePanel(B_SAVE_PANEL)
2659{
2660	fEnclosure = enclosure;
2661	fView = view;
2662	if (enclosure->name)
2663		SetSaveText(enclosure->name);
2664}
2665
2666
2667void
2668TSavePanel::SendMessage(const BMessenger * /* messenger */, BMessage *msg)
2669{
2670	const char	*name = NULL;
2671	BMessage	save(M_SAVE);
2672	entry_ref	ref;
2673
2674	if ((!msg->FindRef("directory", &ref)) && (!msg->FindString("name", &name))) {
2675		save.AddPointer("enclosure", fEnclosure);
2676		save.AddString("name", name);
2677		save.AddRef("directory", &ref);
2678		fView->Window()->PostMessage(&save, fView);
2679	}
2680}
2681
2682
2683void
2684TSavePanel::SetEnclosure(hyper_text *enclosure)
2685{
2686	fEnclosure = enclosure;
2687	if (enclosure->name)
2688		SetSaveText(enclosure->name);
2689	else
2690		SetSaveText("");
2691
2692	if (!IsShowing())
2693		Show();
2694	Window()->Activate();
2695}
2696
2697
2698//--------------------------------------------------------------------
2699//	#pragma mark -
2700
2701
2702void
2703TTextView::InsertText(const char *insertText, int32 length, int32 offset,
2704	const text_run_array *runs)
2705{
2706	ContentChanged();
2707
2708	// Undo function
2709
2710	int32 cursorPos, dummy;
2711	GetSelection(&cursorPos, &dummy);
2712
2713	if (fInputMethodUndoState.active) {
2714		// IMアクティブ時は、一旦別のバッファへ記憶
2715		fInputMethodUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos);
2716		fInputMethodUndoState.replace = false;
2717	} else {
2718		if (fUndoState.replaced) {
2719			fUndoBuffer.AddUndo(insertText, length, offset, K_REPLACED, cursorPos);
2720		} else {
2721			if (length == 1 && insertText[0] == 0x0a)
2722				fUndoBuffer.MakeNewUndoItem();
2723
2724			fUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos);
2725
2726			if (length == 1 && insertText[0] == 0x0a)
2727				fUndoBuffer.MakeNewUndoItem();
2728		}
2729	}
2730
2731	fUndoState.replaced = false;
2732	fUndoState.deleted = false;
2733
2734	struct text_runs : text_run_array { text_run _runs[1]; } style;
2735	if (runs == NULL && IsEditable()) {
2736		style.count = 1;
2737		style.runs[0].offset = 0;
2738		style.runs[0].font = fFont;
2739		style.runs[0].color = kNormalTextColor;
2740		runs = &style;
2741	}
2742
2743	BTextView::InsertText(insertText, length, offset, runs);
2744
2745	if (fSpellCheck && IsEditable())
2746	{
2747		UpdateSpellMarks(offset, length);
2748
2749		rgb_color color;
2750		GetFontAndColor(offset - 1, NULL, &color);
2751		const char *text = Text();
2752
2753		if (length > 1
2754			|| isalpha(text[offset + 1])
2755			|| (!isalpha(text[offset]) && text[offset] != '\'')
2756			|| (color.red == kSpellTextColor.red
2757				&& color.green == kSpellTextColor.green
2758				&& color.blue == kSpellTextColor.blue))
2759		{
2760			int32 start, end;
2761			FindSpellBoundry(length, offset, &start, &end);
2762
2763			DSPELL(printf("Offset %ld, start %ld, end %ld\n", offset, start, end));
2764			DSPELL(printf("\t\"%10.10s...\"\n", text + start));
2765
2766			CheckSpelling(start, end);
2767		}
2768	}
2769}
2770
2771
2772void
2773TTextView::DeleteText(int32 start, int32 finish)
2774{
2775	ContentChanged();
2776
2777	// Undo function
2778	int32 cursorPos, dummy;
2779	GetSelection(&cursorPos, &dummy);
2780	if (fInputMethodUndoState.active) {
2781		if (fInputMethodUndoState.replace) {
2782			fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos);
2783			fInputMethodUndoState.replace = false;
2784		} else {
2785			fInputMethodUndoBuffer.AddUndo(&Text()[start], finish - start, start,
2786				K_DELETED, cursorPos);
2787		}
2788	} else
2789		fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos);
2790
2791	fUndoState.deleted = true;
2792	fUndoState.replaced = true;
2793
2794	BTextView::DeleteText(start, finish);
2795	if (fSpellCheck && IsEditable()) {
2796		UpdateSpellMarks(start, start - finish);
2797
2798		int32 s, e;
2799		FindSpellBoundry(1, start, &s, &e);
2800		CheckSpelling(s, e);
2801	}
2802}
2803
2804
2805void
2806TTextView::ContentChanged(void)
2807{
2808	BLooper *looper = Looper();
2809	if (looper == NULL)
2810		return;
2811
2812	BMessage msg(FIELD_CHANGED);
2813	msg.AddInt32("bitmask", FIELD_BODY);
2814	msg.AddPointer("source", this);
2815	looper->PostMessage(&msg);
2816}
2817
2818
2819void
2820TTextView::CheckSpelling(int32 start, int32 end, int32 flags)
2821{
2822	const char 	*text = Text();
2823	const char 	*next, *endPtr, *word = NULL;
2824	int32 		wordLength = 0, wordOffset;
2825	int32		nextHighlight = start;
2826	BString 	testWord;
2827	bool		isCap = false;
2828	bool		isAlpha;
2829	bool		isApost;
2830
2831	for (next = text + start, endPtr = text + end; next <= endPtr; next++) {
2832		//printf("next=%c\n", *next);
2833		// ToDo: this has to be refined to other languages...
2834		// Alpha signifies the start of a word
2835		isAlpha = isalpha(*next);
2836		isApost = (*next == '\'');
2837		if (!word && isAlpha) {
2838			//printf("Found word start\n");
2839			word = next;
2840			wordLength++;
2841			isCap = isupper(*word);
2842		} else if (word && (isAlpha || isApost) && !(isApost && !isalpha(next[1]))
2843					&& !(isCap && isApost && (next[1] == 's'))) {
2844			// Word continues check
2845			wordLength++;
2846			//printf("Word continues...\n");
2847		} else if (word) {
2848			// End of word reached
2849
2850			//printf("Word End\n");
2851			// Don't check single characters
2852			if (wordLength > 1) {
2853				bool isUpper = true;
2854
2855				// Look for all uppercase
2856				for (int32 i = 0; i < wordLength; i++) {
2857					if (word[i] == '\'')
2858						break;
2859
2860					if (islower(word[i])) {
2861						isUpper = false;
2862						break;
2863					}
2864				}
2865
2866				// Don't check all uppercase words
2867				if (!isUpper) {
2868					bool foundMatch = false;
2869					wordOffset = word - text;
2870					testWord.SetTo(word, wordLength);
2871
2872					testWord = testWord.ToLower();
2873					DSPELL(printf("Testing: \"%s\"\n", testWord.String()));
2874
2875					int32 key = -1;
2876					if (gDictCount)
2877						key = gExactWords[0]->GetKey(testWord.String());
2878
2879					// Search all dictionaries
2880					for (int32 i = 0; i < gDictCount; i++) {
2881						if (gExactWords[i]->Lookup(key) >= 0) {
2882							foundMatch = true;
2883							break;
2884						}
2885					}
2886
2887					if (!foundMatch) {
2888						if (flags & S_CLEAR_ERRORS)
2889							RemoveSpellMark(nextHighlight, wordOffset);
2890
2891						if (flags & S_SHOW_ERRORS)
2892							AddSpellMark(wordOffset, wordOffset + wordLength);
2893					} else if (flags & S_CLEAR_ERRORS)
2894						RemoveSpellMark(nextHighlight, wordOffset + wordLength);
2895
2896					nextHighlight = wordOffset + wordLength;
2897				}
2898			}
2899			// Reset state to looking for word
2900			word = NULL;
2901			wordLength = 0;
2902		}
2903	}
2904
2905	if (nextHighlight <= end
2906		&& (flags & S_CLEAR_ERRORS) != 0
2907		&& nextHighlight < TextLength())
2908		SetFontAndColor(nextHighlight, end, NULL, B_FONT_ALL, &kNormalTextColor);
2909}
2910
2911
2912void
2913TTextView::FindSpellBoundry(int32 length, int32 offset, int32 *_start, int32 *_end)
2914{
2915	int32 start, end, textLength;
2916	const char *text = Text();
2917	textLength = TextLength();
2918
2919	for (start = offset - 1; start >= 0
2920		&& (isalpha(text[start]) || text[start] == '\''); start--) {}
2921
2922	start++;
2923
2924	for (end = offset + length; end < textLength
2925		&& (isalpha(text[end]) || text[end] == '\''); end++) {}
2926
2927	*_start = start;
2928	*_end = end;
2929}
2930
2931
2932TTextView::spell_mark *
2933TTextView::FindSpellMark(int32 start, int32 end, spell_mark **_previousMark)
2934{
2935	spell_mark *lastMark = NULL;
2936
2937	for (spell_mark *spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) {
2938		if (spellMark->start < end && spellMark->end > start) {
2939			if (_previousMark)
2940				*_previousMark = lastMark;
2941			return spellMark;
2942		}
2943
2944		lastMark = spellMark;
2945	}
2946	return NULL;
2947}
2948
2949
2950void
2951TTextView::UpdateSpellMarks(int32 offset, int32 length)
2952{
2953	DSPELL(printf("UpdateSpellMarks: offset = %ld, length = %ld\n", offset, length));
2954
2955	spell_mark *spellMark;
2956	for (spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) {
2957		DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end));
2958
2959		if (spellMark->end < offset)
2960			continue;
2961
2962		if (spellMark->start > offset)
2963			spellMark->start += length;
2964
2965		spellMark->end += length;
2966
2967		DSPELL(printf("\t-> reset: %ld - %ld\n", spellMark->start, spellMark->end));
2968	}
2969}
2970
2971
2972status_t
2973TTextView::AddSpellMark(int32 start, int32 end)
2974{
2975	DSPELL(printf("AddSpellMark: start = %ld, end = %ld\n", start, end));
2976
2977	// check if there is already a mark for this passage
2978	spell_mark *spellMark = FindSpellMark(start, end);
2979	if (spellMark) {
2980		if (spellMark->start == start && spellMark->end == end) {
2981			DSPELL(printf("\tfound one\n"));
2982			return B_OK;
2983		}
2984
2985		DSPELL(printf("\tremove old one\n"));
2986		RemoveSpellMark(start, end);
2987	}
2988
2989	spellMark = (spell_mark *)malloc(sizeof(spell_mark));
2990	if (spellMark == NULL)
2991		return B_NO_MEMORY;
2992
2993	spellMark->start = start;
2994	spellMark->end = end;
2995	spellMark->style = RunArray(start, end);
2996
2997	// set the spell marks appearance
2998	BFont font(fFont);
2999	font.SetFace(B_BOLD_FACE | B_ITALIC_FACE);
3000	SetFontAndColor(start, end, &font, B_FONT_ALL, &kSpellTextColor);
3001
3002	// add it to the queue
3003	spellMark->next = fFirstSpellMark;
3004	fFirstSpellMark = spellMark;
3005
3006	return B_OK;
3007}
3008
3009
3010bool
3011TTextView::RemoveSpellMark(int32 start, int32 end)
3012{
3013	DSPELL(printf("RemoveSpellMark: start = %ld, end = %ld\n", start, end));
3014
3015	// find spell mark
3016	spell_mark *lastMark = NULL;
3017	spell_mark *spellMark = FindSpellMark(start, end, &lastMark);
3018	if (spellMark == NULL) {
3019		DSPELL(printf("\tnot found!\n"));
3020		return false;
3021	}
3022
3023	DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end));
3024
3025	// dequeue the spell mark
3026	if (lastMark)
3027		lastMark->next = spellMark->next;
3028	else
3029		fFirstSpellMark = spellMark->next;
3030
3031	if (spellMark->start < start)
3032		start = spellMark->start;
3033	if (spellMark->end > end)
3034		end = spellMark->end;
3035
3036	// reset old text run array
3037	SetRunArray(start, end, spellMark->style);
3038
3039	free(spellMark->style);
3040	free(spellMark);
3041
3042	return true;
3043}
3044
3045
3046void
3047TTextView::RemoveSpellMarks()
3048{
3049	spell_mark *spellMark, *nextMark;
3050
3051	for (spellMark = fFirstSpellMark; spellMark; spellMark = nextMark) {
3052		nextMark = spellMark->next;
3053
3054		// reset old text run array
3055		SetRunArray(spellMark->start, spellMark->end, spellMark->style);
3056
3057		free(spellMark->style);
3058		free(spellMark);
3059	}
3060
3061	fFirstSpellMark = NULL;
3062}
3063
3064
3065void
3066TTextView::EnableSpellCheck(bool enable)
3067{
3068	if (fSpellCheck == enable)
3069		return;
3070
3071	fSpellCheck = enable;
3072	int32 textLength = TextLength();
3073	if (fSpellCheck) {
3074		// work-around for a bug in the BTextView class
3075		// which causes lots of flicker
3076		int32 start, end;
3077		GetSelection(&start, &end);
3078		if (start != end)
3079			Select(start, start);
3080
3081		CheckSpelling(0, textLength);
3082
3083		if (start != end)
3084			Select(start, end);
3085	}
3086	else
3087		RemoveSpellMarks();
3088}
3089
3090
3091void
3092TTextView::WindowActivated(bool flag)
3093{
3094	if (!flag) {
3095		// WindowActivated(false) は、IM も Inactive になり、そのまま確定される。
3096		// しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED)
3097		// を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。
3098		// OpenBeOSで修正されることを願って暫定処置としている。
3099		fInputMethodUndoState.active = false;
3100		// fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加
3101		if (fInputMethodUndoBuffer.CountItems() > 0) {
3102			KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1);
3103			if (item->History == K_INSERTED) {
3104				fUndoBuffer.MakeNewUndoItem();
3105				fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset,
3106					item->History, item->CursorPos);
3107				fUndoBuffer.MakeNewUndoItem();
3108			}
3109			fInputMethodUndoBuffer.MakeEmpty();
3110		}
3111	}
3112	BTextView::WindowActivated(flag);
3113}
3114
3115
3116void
3117TTextView::AddQuote(int32 start, int32 finish)
3118{
3119	BRect rect = Bounds();
3120
3121	int32 lineStart;
3122	GoToLine(CurrentLine());
3123	GetSelection(&lineStart, &lineStart);
3124
3125	// make sure that we're changing the whole last line, too
3126	int32 lineEnd = finish > lineStart ? finish - 1 : finish;
3127	{
3128		const char *text = Text();
3129		while (text[lineEnd] && text[lineEnd] != '\n')
3130			lineEnd++;
3131	}
3132	Select(lineStart, lineEnd);
3133
3134	int32 textLength = lineEnd - lineStart;
3135	char *text = (char *)malloc(textLength + 1);
3136	if (text == NULL)
3137		return;
3138
3139	GetText(lineStart, textLength, text);
3140
3141	int32 quoteLength = strlen(QUOTE);
3142	int32 targetLength = 0;
3143	char *target = NULL;
3144	int32 lastLine = 0;
3145
3146	for (int32 index = 0; index < textLength; index++) {
3147		if (text[index] == '\n' || index == textLength - 1) {
3148			// add quote to this line
3149			int32 lineLength = index - lastLine + 1;
3150
3151			target = (char *)realloc(target, targetLength + lineLength + quoteLength);
3152			if (target == NULL) {
3153				// free the old buffer?
3154				free(text);
3155				return;
3156			}
3157
3158			// copy the quote sign
3159			memcpy(&target[targetLength], QUOTE, quoteLength);
3160			targetLength += quoteLength;
3161
3162			// copy the rest of the line
3163			memcpy(&target[targetLength], &text[lastLine], lineLength);
3164			targetLength += lineLength;
3165
3166			lastLine = index + 1;
3167		}
3168	}
3169
3170	// replace with quoted text
3171	free(text);
3172	Delete();
3173
3174	if (fColoredQuotes) {
3175		const BFont *font = Font();
3176		TextRunArray style(targetLength / 8 + 8);
3177
3178		FillInQuoteTextRuns(NULL, NULL, target, targetLength, font,
3179			&style.Array(), style.MaxEntries());
3180		Insert(target, targetLength, &style.Array());
3181	} else
3182		Insert(target, targetLength);
3183
3184	free(target);
3185
3186	// redo the old selection (compute the new start if necessary)
3187	Select(start + quoteLength, finish + (targetLength - textLength));
3188
3189	ScrollTo(rect.LeftTop());
3190}
3191
3192
3193void
3194TTextView::RemoveQuote(int32 start, int32 finish)
3195{
3196	BRect rect = Bounds();
3197
3198	GoToLine(CurrentLine());
3199	int32 lineStart;
3200	GetSelection(&lineStart, &lineStart);
3201
3202	// make sure that we're changing the whole last line, too
3203	int32 lineEnd = finish > lineStart ? finish - 1 : finish;
3204	const char *text = Text();
3205	while (text[lineEnd] && text[lineEnd] != '\n')
3206		lineEnd++;
3207
3208	Select(lineStart, lineEnd);
3209
3210	int32 length = lineEnd - lineStart;
3211	char *target = (char *)malloc(length + 1);
3212	if (target == NULL)
3213		return;
3214
3215	int32 quoteLength = strlen(QUOTE);
3216	int32 removed = 0;
3217	text += lineStart;
3218
3219	for (int32 index = 0; index < length;) {
3220		// find out the length of the current line
3221		int32 lineLength = 0;
3222		while (index + lineLength < length && text[lineLength] != '\n')
3223			lineLength++;
3224
3225		// include the newline to be part of this line
3226		if (text[lineLength] == '\n' && index + lineLength + 1 < length)
3227			lineLength++;
3228
3229		if (!strncmp(text, QUOTE, quoteLength)) {
3230			// remove quote
3231			length -= quoteLength;
3232			removed += quoteLength;
3233
3234			lineLength -= quoteLength;
3235			text += quoteLength;
3236		}
3237
3238		if (lineLength == 0) {
3239			target[index] = '\0';
3240			break;
3241		}
3242
3243		memcpy(&target[index], text, lineLength);
3244
3245		text += lineLength;
3246		index += lineLength;
3247	}
3248
3249	if (removed) {
3250		Delete();
3251
3252		if (fColoredQuotes) {
3253			const BFont *font = Font();
3254			TextRunArray style(length / 8 + 8);
3255
3256			FillInQuoteTextRuns(NULL, NULL, target, length, font,
3257				&style.Array(), style.MaxEntries());
3258			Insert(target, length, &style.Array());
3259		} else
3260			Insert(target, length);
3261
3262		// redo old selection
3263		bool noSelection = start == finish;
3264
3265		if (start > lineStart + quoteLength)
3266			start -= quoteLength;
3267		else
3268			start = lineStart;
3269
3270		if (noSelection)
3271			finish = start;
3272		else
3273			finish -= removed;
3274	}
3275
3276	free(target);
3277
3278	Select(start, finish);
3279	ScrollTo(rect.LeftTop());
3280}
3281
3282
3283void
3284TTextView::Undo(BClipboard */*clipboard*/)
3285{
3286	if (fInputMethodUndoState.active)
3287		return;
3288
3289	int32 length, offset, cursorPos;
3290	undo_type history;
3291	char *text;
3292	status_t status;
3293
3294	status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos);
3295	if (status == B_OK) {
3296		fUndoBuffer.Off();
3297
3298		switch (history) {
3299			case K_INSERTED:
3300				BTextView::Delete(offset, offset + length);
3301				Select(offset, offset);
3302				break;
3303
3304			case K_DELETED:
3305				BTextView::Insert(offset, text, length);
3306				Select(offset, offset + length);
3307				break;
3308
3309			case K_REPLACED:
3310				BTextView::Delete(offset, offset + length);
3311				status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos);
3312				if (status == B_OK && history == K_DELETED) {
3313					BTextView::Insert(offset, text, length);
3314					Select(offset, offset + length);
3315				} else {
3316					::beep();
3317					BAlert* alert = new BAlert("",
3318						B_TRANSLATE("Inconsistency occurred in the undo/redo "
3319							"buffer."),	B_TRANSLATE("OK"));
3320					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3321					alert->Go();
3322				}
3323				break;
3324		}
3325		ScrollToSelection();
3326		ContentChanged();
3327		fUndoBuffer.On();
3328	}
3329}
3330
3331
3332void
3333TTextView::Redo()
3334{
3335	if (fInputMethodUndoState.active)
3336		return;
3337
3338	int32 length, offset, cursorPos;
3339	undo_type history;
3340	char *text;
3341	status_t status;
3342	bool replaced;
3343
3344	status = fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced);
3345	if (status == B_OK) {
3346		fUndoBuffer.Off();
3347
3348		switch (history) {
3349			case K_INSERTED:
3350				BTextView::Insert(offset, text, length);
3351				Select(offset, offset + length);
3352				break;
3353
3354			case K_DELETED:
3355				BTextView::Delete(offset, offset + length);
3356				if (replaced) {
3357					fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced);
3358					BTextView::Insert(offset, text, length);
3359				}
3360				Select(offset, offset + length);
3361				break;
3362
3363			case K_REPLACED:
3364				::beep();
3365				BAlert* alert = new BAlert("",
3366					B_TRANSLATE("Inconsistency occurred in the undo/redo "
3367						"buffer."),	B_TRANSLATE("OK"));
3368				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3369				alert->Go();
3370				break;
3371		}
3372		ScrollToSelection();
3373		ContentChanged();
3374		fUndoBuffer.On();
3375	}
3376}
3377
3378