1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2001, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
23CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or
30registered trademarks of Be Incorporated in the United States and other
31countries. Other brand product names are registered trademarks or trademarks
32of their respective holders. All rights reserved.
33*/
34
35
36#include <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
86const rgb_color kSpellTextColor = {255, 0, 0, 255};
87const rgb_color kHeaderColor = {72, 72, 72, 255};
88
89const rgb_color kQuoteColors[] = {
90	{0, 0, 0x80, 0},		// 3rd, 6th, ... quote level color (blue)
91	{0, 0x80, 0, 0},		// 1st, 4th, ... quote level color (green)
92	{0x80, 0, 0, 0}			// 2nd, ... (red)
93};
94const int32 kNumQuoteColors = 3;
95
96const rgb_color kDiffColors[] = {
97	{0xb0, 0, 0, 0},		// '-', red
98	{0, 0x90, 0, 0},		// '+', green
99	{0x6a, 0x6a, 0x6a, 0}	// '@@', dark grey
100};
101
102void Unicode2UTF8(int32 c, char **out);
103
104
105inline bool
106IsInitialUTF8Byte(uchar b)
107{
108	return ((b & 0xC0) != 0x80);
109}
110
111
112void
113Unicode2UTF8(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
138static bool
139FilterHTMLTag(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 */
281static uint8
282FindURL(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
334static void
335CopyQuotes(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
363int32
364diff_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
380bool
381is_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*/
393void
394FillInQuoteTextRuns(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
488					? kQuoteColors[level % kNumQuoteColors] : 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
515					? kQuoteColors[level % kNumQuoteColors] : 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
565TextRunArray::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
575TextRunArray::~TextRunArray()
576{
577	free(fArray);
578}
579
580
581//	#pragma mark -
582
583
584TContentView::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
605void
606TContentView::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
671void
672TContentView::Focus(bool focus)
673{
674	if (fFocus != focus) {
675		fFocus = focus;
676		Draw(Frame());
677	}
678}
679
680
681void
682TContentView::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
786TTextView::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
844TTextView::~TTextView()
845{
846	ClearList();
847	delete fPanel;
848
849	if (fYankBuffer)
850		free(fYankBuffer);
851
852	delete_sem(fStopSem);
853}
854
855
856void
857TTextView::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
871void
872TTextView::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
886void
887TTextView::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
1086void
1087TTextView::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
1112void
1113TTextView::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
1414void
1415TTextView::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
1706void
1707TTextView::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
1730void
1731TTextView::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
1754void
1755TTextView::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
1781void
1782TTextView::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
1885status_t
1886TTextView::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
1991void
1992TTextView::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
2011bool
2012TTextView::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
2025void
2026TTextView::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
2209TTextView::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
2227bool
2228TTextView::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
2323bool
2324TTextView::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
2395bool
2396TTextView::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
2432status_t
2433TTextView::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
2573done:
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
2586status_t
2587TTextView::Reader::Unlock()
2588{
2589	return release_sem(fStopSem);
2590}
2591
2592
2593bool
2594TTextView::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
2607TSavePanel::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
2617void
2618TSavePanel::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
2633void
2634TSavePanel::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
2652void
2653TTextView::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
2722void
2723TTextView::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
2755void
2756TTextView::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
2769void
2770TTextView::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
2864void
2865TTextView::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
2884TTextView::spell_mark *
2885TTextView::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
2902void
2903TTextView::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
2924status_t
2925TTextView::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
2962bool
2963TTextView::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
2998void
2999TTextView::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
3017void
3018TTextView::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
3043void
3044TTextView::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
3068void
3069TTextView::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
3148void
3149TTextView::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
3239int32
3240TTextView::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
3255int32
3256TTextView::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
3272void
3273TTextView::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
3321void
3322TTextView::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