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