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