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