1/*
2 * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan A��mus, superstippi@gmx.de
7 *		Stefano Ceccherini, stefano.ceccherini@gmail.com
8 *		Marc Flerackers, mflerackers@androme.be
9 *		Hiroshi Lockheimer (BTextView is based on his STEEngine)
10 *		John Scipione, jscipione@gmail.com
11 *		Oliver Tappe, zooey@hirschkaefer.de
12 */
13
14
15// TODOs:
16// - Consider using BObjectList instead of BList
17// 	 for disallowed characters (it would remove a lot of reinterpret_casts)
18// - Check for correctness and possible optimizations the calls to _Refresh(),
19// 	 to refresh only changed parts of text (currently we often redraw the whole
20//   text)
21
22// Known Bugs:
23// - Double buffering doesn't work well (disabled by default)
24
25
26#include <TextView.h>
27
28#include <new>
29
30#include <stdio.h>
31#include <stdlib.h>
32
33#include <Application.h>
34#include <Beep.h>
35#include <Bitmap.h>
36#include <Clipboard.h>
37#include <Debug.h>
38#include <Entry.h>
39#include <Input.h>
40#include <LayoutBuilder.h>
41#include <LayoutUtils.h>
42#include <MessageRunner.h>
43#include <Path.h>
44#include <PopUpMenu.h>
45#include <PropertyInfo.h>
46#include <Region.h>
47#include <ScrollBar.h>
48#include <SystemCatalog.h>
49#include <Window.h>
50
51#include <binary_compatibility/Interface.h>
52
53#include "InlineInput.h"
54#include "LineBuffer.h"
55#include "StyleBuffer.h"
56#include "TextGapBuffer.h"
57#include "UndoBuffer.h"
58#include "WidthBuffer.h"
59
60
61using namespace std;
62using BPrivate::gSystemCatalog;
63
64
65#undef B_TRANSLATION_CONTEXT
66#define B_TRANSLATION_CONTEXT "TextView"
67
68
69#define TRANSLATE(str) \
70	gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "TextView")
71
72#undef TRACE
73#undef CALLED
74//#define TRACE_TEXT_VIEW
75#ifdef TRACE_TEXT_VIEW
76#	include <FunctionTracer.h>
77	static int32 sFunctionDepth = -1;
78#	define CALLED(x...)	FunctionTracer _ft("BTextView", __FUNCTION__, \
79							sFunctionDepth)
80#	define TRACE(x...)	{ BString _to; \
81							_to.Append(' ', (sFunctionDepth + 1) * 2); \
82							printf("%s", _to.String()); printf(x); }
83#else
84#	define CALLED(x...)
85#	define TRACE(x...)
86#endif
87
88
89#define USE_WIDTHBUFFER 1
90#define USE_DOUBLEBUFFERING 0
91
92
93struct flattened_text_run {
94	int32	offset;
95	font_family	family;
96	font_style style;
97	float	size;
98	float	shear;		// typically 90.0
99	uint16	face;		// typically 0
100	uint8	red;
101	uint8	green;
102	uint8	blue;
103	uint8	alpha;		// 255 == opaque
104	uint16	_reserved_;	// 0
105};
106
107struct flattened_text_run_array {
108	uint32	magic;
109	uint32	version;
110	int32	count;
111	flattened_text_run styles[1];
112};
113
114static const uint32 kFlattenedTextRunArrayMagic = 'Ali!';
115static const uint32 kFlattenedTextRunArrayVersion = 0;
116
117
118enum {
119	CHAR_CLASS_DEFAULT,
120	CHAR_CLASS_WHITESPACE,
121	CHAR_CLASS_GRAPHICAL,
122	CHAR_CLASS_QUOTE,
123	CHAR_CLASS_PUNCTUATION,
124	CHAR_CLASS_PARENS_OPEN,
125	CHAR_CLASS_PARENS_CLOSE,
126	CHAR_CLASS_END_OF_TEXT
127};
128
129
130class BTextView::TextTrackState {
131public:
132	TextTrackState(BMessenger messenger);
133	~TextTrackState();
134
135	void SimulateMouseMovement(BTextView* view);
136
137public:
138	int32				clickOffset;
139	bool				shiftDown;
140	BRect				selectionRect;
141	BPoint				where;
142
143	int32				anchor;
144	int32				selStart;
145	int32				selEnd;
146
147private:
148	BMessageRunner*		fRunner;
149};
150
151
152struct BTextView::LayoutData {
153	LayoutData()
154		: leftInset(0),
155		  topInset(0),
156		  rightInset(0),
157		  bottomInset(0),
158		  valid(false)
159	{
160	}
161
162	void UpdateInsets(const BRect& bounds, const BRect& textRect)
163	{
164		// we disallow negative insets, as they would cause parts of the
165		// text to be hidden
166		leftInset = textRect.left >= bounds.left
167			? textRect.left - bounds.left
168			: 0;
169		topInset = textRect.top >= bounds.top
170			? textRect.top - bounds.top
171			: 0;
172		rightInset = bounds.right >= textRect.right
173			? bounds.right - textRect.right
174			: leftInset;
175		bottomInset = bounds.bottom >= textRect.bottom
176			? bounds.bottom - textRect.bottom
177			: topInset;
178	}
179
180	float				leftInset;
181	float				topInset;
182	float				rightInset;
183	float				bottomInset;
184
185	BSize				min;
186	BSize				preferred;
187	bool				valid;
188};
189
190
191static const rgb_color kBlueInputColor = { 152, 203, 255, 255 };
192static const rgb_color kRedInputColor = { 255, 152, 152, 255 };
193
194static const float kHorizontalScrollBarStep = 10.0;
195static const float kVerticalScrollBarStep = 12.0;
196
197static const int32 kMsgNavigateArrow = '_NvA';
198static const int32 kMsgNavigatePage  = '_NvP';
199static const int32 kMsgRemoveWord    = '_RmW';
200
201
202static property_info sPropertyList[] = {
203	{
204		"selection",
205		{ B_GET_PROPERTY, 0 },
206		{ B_DIRECT_SPECIFIER, 0 },
207		"Returns the current selection.", 0,
208		{ B_INT32_TYPE, 0 }
209	},
210	{
211		"selection",
212		{ B_SET_PROPERTY, 0 },
213		{ B_DIRECT_SPECIFIER, 0 },
214		"Sets the current selection.", 0,
215		{ B_INT32_TYPE, 0 }
216	},
217	{
218		"Text",
219		{ B_COUNT_PROPERTIES, 0 },
220		{ B_DIRECT_SPECIFIER, 0 },
221		"Returns the length of the text in bytes.", 0,
222		{ B_INT32_TYPE, 0 }
223	},
224	{
225		"Text",
226		{ B_GET_PROPERTY, 0 },
227		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
228		"Returns the text in the specified range in the BTextView.", 0,
229		{ B_STRING_TYPE, 0 }
230	},
231	{
232		"Text",
233		{ B_SET_PROPERTY, 0 },
234		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
235		"Removes or inserts text into the specified range in the BTextView.", 0,
236		{ B_STRING_TYPE, 0 }
237	},
238	{
239		"text_run_array",
240		{ B_GET_PROPERTY, 0 },
241		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
242		"Returns the style information for the text in the specified range in "
243			"the BTextView.", 0,
244		{ B_RAW_TYPE, 0 },
245	},
246	{
247		"text_run_array",
248		{ B_SET_PROPERTY, 0 },
249		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
250		"Sets the style information for the text in the specified range in the "
251			"BTextView.", 0,
252		{ B_RAW_TYPE, 0 },
253	},
254
255	{ 0 }
256};
257
258
259BTextView::BTextView(BRect frame, const char* name, BRect textRect,
260	uint32 resizeMask, uint32 flags)
261	:
262	BView(frame, name, resizeMask,
263		flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
264{
265	_InitObject(textRect, NULL, NULL);
266	SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
267}
268
269
270BTextView::BTextView(BRect frame, const char* name, BRect textRect,
271	const BFont* initialFont, const rgb_color* initialColor,
272	uint32 resizeMask, uint32 flags)
273	:
274	BView(frame, name, resizeMask,
275		flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
276{
277	_InitObject(textRect, initialFont, initialColor);
278	SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
279}
280
281
282BTextView::BTextView(const char* name, uint32 flags)
283	:
284	BView(name,
285		flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
286{
287	_InitObject(Bounds(), NULL, NULL);
288	SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
289}
290
291
292BTextView::BTextView(const char* name, const BFont* initialFont,
293	const rgb_color* initialColor, uint32 flags)
294	:
295	BView(name,
296		flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
297{
298	_InitObject(Bounds(), initialFont, initialColor);
299	SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
300}
301
302
303BTextView::BTextView(BMessage* archive)
304	:
305	BView(archive)
306{
307	CALLED();
308	BRect rect;
309
310	if (archive->FindRect("_trect", &rect) != B_OK)
311		rect.Set(0, 0, 0, 0);
312
313	_InitObject(rect, NULL, NULL);
314
315	bool toggle;
316
317	if (archive->FindBool("_password", &toggle) == B_OK)
318		HideTyping(toggle);
319
320	const char* text = NULL;
321	if (archive->FindString("_text", &text) == B_OK)
322		SetText(text);
323
324	int32 flag, flag2;
325	if (archive->FindInt32("_align", &flag) == B_OK)
326		SetAlignment((alignment)flag);
327
328	float value;
329
330	if (archive->FindFloat("_tab", &value) == B_OK)
331		SetTabWidth(value);
332
333	if (archive->FindInt32("_col_sp", &flag) == B_OK)
334		SetColorSpace((color_space)flag);
335
336	if (archive->FindInt32("_max", &flag) == B_OK)
337		SetMaxBytes(flag);
338
339	if (archive->FindInt32("_sel", &flag) == B_OK &&
340		archive->FindInt32("_sel", &flag2) == B_OK)
341		Select(flag, flag2);
342
343	if (archive->FindBool("_stylable", &toggle) == B_OK)
344		SetStylable(toggle);
345
346	if (archive->FindBool("_auto_in", &toggle) == B_OK)
347		SetAutoindent(toggle);
348
349	if (archive->FindBool("_wrap", &toggle) == B_OK)
350		SetWordWrap(toggle);
351
352	if (archive->FindBool("_nsel", &toggle) == B_OK)
353		MakeSelectable(!toggle);
354
355	if (archive->FindBool("_nedit", &toggle) == B_OK)
356		MakeEditable(!toggle);
357
358	ssize_t disallowedCount = 0;
359	const int32* disallowedChars = NULL;
360	if (archive->FindData("_dis_ch", B_RAW_TYPE,
361		(const void**)&disallowedChars, &disallowedCount) == B_OK) {
362
363		fDisallowedChars = new BList;
364		disallowedCount /= sizeof(int32);
365		for (int32 x = 0; x < disallowedCount; x++) {
366			fDisallowedChars->AddItem(
367				reinterpret_cast<void*>(disallowedChars[x]));
368		}
369	}
370
371	ssize_t runSize = 0;
372	const void* flattenedRun = NULL;
373
374	if (archive->FindData("_runs", B_RAW_TYPE, &flattenedRun, &runSize)
375			== B_OK) {
376		text_run_array* runArray = UnflattenRunArray(flattenedRun,
377			(int32*)&runSize);
378		if (runArray) {
379			SetRunArray(0, TextLength(), runArray);
380			FreeRunArray(runArray);
381		}
382	}
383}
384
385
386BTextView::~BTextView()
387{
388	_CancelInputMethod();
389	_StopMouseTracking();
390	_DeleteOffscreen();
391
392	delete fText;
393	delete fLines;
394	delete fStyles;
395	delete fDisallowedChars;
396	delete fUndo;
397	delete fClickRunner;
398	delete fDragRunner;
399	delete fLayoutData;
400}
401
402
403BArchivable*
404BTextView::Instantiate(BMessage* archive)
405{
406	CALLED();
407	if (validate_instantiation(archive, "BTextView"))
408		return new BTextView(archive);
409	return NULL;
410}
411
412
413status_t
414BTextView::Archive(BMessage* data, bool deep) const
415{
416	CALLED();
417	status_t err = BView::Archive(data, deep);
418	if (err == B_OK)
419		err = data->AddString("_text", Text());
420	if (err == B_OK)
421		err = data->AddInt32("_align", fAlignment);
422	if (err == B_OK)
423		err = data->AddFloat("_tab", fTabWidth);
424	if (err == B_OK)
425		err = data->AddInt32("_col_sp", fColorSpace);
426	if (err == B_OK)
427		err = data->AddRect("_trect", fTextRect);
428	if (err == B_OK)
429		err = data->AddInt32("_max", fMaxBytes);
430	if (err == B_OK)
431		err = data->AddInt32("_sel", fSelStart);
432	if (err == B_OK)
433		err = data->AddInt32("_sel", fSelEnd);
434	if (err == B_OK)
435		err = data->AddBool("_stylable", fStylable);
436	if (err == B_OK)
437		err = data->AddBool("_auto_in", fAutoindent);
438	if (err == B_OK)
439		err = data->AddBool("_wrap", fWrap);
440	if (err == B_OK)
441		err = data->AddBool("_nsel", !fSelectable);
442	if (err == B_OK)
443		err = data->AddBool("_nedit", !fEditable);
444	if (err == B_OK)
445		err = data->AddBool("_password", IsTypingHidden());
446
447	if (err == B_OK && fDisallowedChars != NULL && fDisallowedChars->CountItems() > 0) {
448		err = data->AddData("_dis_ch", B_RAW_TYPE, fDisallowedChars->Items(),
449			fDisallowedChars->CountItems() * sizeof(int32));
450	}
451
452	if (err == B_OK) {
453		int32 runSize = 0;
454		text_run_array* runArray = RunArray(0, TextLength());
455
456		void* flattened = FlattenRunArray(runArray, &runSize);
457		if (flattened != NULL) {
458			data->AddData("_runs", B_RAW_TYPE, flattened, runSize);
459			free(flattened);
460		} else
461			err = B_NO_MEMORY;
462
463		FreeRunArray(runArray);
464	}
465
466	return err;
467}
468
469
470void
471BTextView::AttachedToWindow()
472{
473	BView::AttachedToWindow();
474
475	SetDrawingMode(B_OP_COPY);
476
477	Window()->SetPulseRate(500000);
478
479	fCaretVisible = false;
480	fCaretTime = 0;
481	fClickCount = 0;
482	fClickTime = 0;
483	fDragOffset = -1;
484	fActive = false;
485
486	_AutoResize(true);
487
488	_UpdateScrollbars();
489
490	SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
491}
492
493
494void
495BTextView::DetachedFromWindow()
496{
497	BView::DetachedFromWindow();
498}
499
500
501void
502BTextView::Draw(BRect updateRect)
503{
504	// what lines need to be drawn?
505	int32 startLine = _LineAt(BPoint(0.0, updateRect.top));
506	int32 endLine = _LineAt(BPoint(0.0, updateRect.bottom));
507
508	_DrawLines(startLine, endLine, -1, true);
509}
510
511
512void
513BTextView::MouseDown(BPoint where)
514{
515	// should we even bother?
516	if (!fEditable && !fSelectable)
517		return;
518
519	_CancelInputMethod();
520
521	if (!IsFocus())
522		MakeFocus();
523
524	_HideCaret();
525
526	_StopMouseTracking();
527
528	int32 modifiers = 0;
529	uint32 buttons = 0;
530	BMessage* currentMessage = Window()->CurrentMessage();
531	if (currentMessage != NULL) {
532		currentMessage->FindInt32("modifiers", &modifiers);
533		currentMessage->FindInt32("buttons", (int32*)&buttons);
534	}
535
536	if (buttons == B_SECONDARY_MOUSE_BUTTON) {
537		_ShowContextMenu(where);
538		return;
539	}
540
541	BMessenger messenger(this);
542	fTrackingMouse = new (nothrow) TextTrackState(messenger);
543	if (fTrackingMouse == NULL)
544		return;
545
546	fTrackingMouse->clickOffset = OffsetAt(where);
547	fTrackingMouse->shiftDown = modifiers & B_SHIFT_KEY;
548	fTrackingMouse->where = where;
549
550	bigtime_t clickTime = system_time();
551	bigtime_t clickSpeed = 0;
552	get_click_speed(&clickSpeed);
553	bool multipleClick
554		= clickTime - fClickTime < clickSpeed
555			&& fLastClickOffset == fTrackingMouse->clickOffset;
556
557	fWhere = where;
558
559	SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS,
560		B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
561
562	if (fSelStart != fSelEnd && !fTrackingMouse->shiftDown && !multipleClick) {
563		BRegion region;
564		GetTextRegion(fSelStart, fSelEnd, &region);
565		if (region.Contains(where)) {
566			// Setup things for dragging
567			fTrackingMouse->selectionRect = region.Frame();
568			fClickCount = 1;
569			fClickTime = clickTime;
570			fLastClickOffset = OffsetAt(where);
571			return;
572		}
573	}
574
575	if (multipleClick) {
576		if (fClickCount > 3) {
577			fClickCount = 0;
578			fClickTime = 0;
579		} else {
580			fClickCount++;
581			fClickTime = clickTime;
582		}
583	} else if (!fTrackingMouse->shiftDown) {
584		// If no multiple click yet and shift is not pressed, this is an
585		// independent first click somewhere into the textview - we initialize
586		// the corresponding members for handling potential multiple clicks:
587		fLastClickOffset = fCaretOffset = fTrackingMouse->clickOffset;
588		fClickCount = 1;
589		fClickTime = clickTime;
590
591		// Deselect any previously selected text
592		Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset);
593	}
594
595	if (fClickTime == clickTime) {
596		BMessage message(_PING_);
597		message.AddInt64("clickTime", clickTime);
598		delete fClickRunner;
599
600		BMessenger messenger(this);
601		fClickRunner = new (nothrow) BMessageRunner(messenger, &message,
602			clickSpeed, 1);
603	}
604
605	if (!fSelectable) {
606		_StopMouseTracking();
607		return;
608	}
609
610	int32 offset = fSelStart;
611	if (fTrackingMouse->clickOffset > fSelStart)
612		offset = fSelEnd;
613
614	fTrackingMouse->anchor = offset;
615
616	MouseMoved(where, B_INSIDE_VIEW, NULL);
617}
618
619
620void
621BTextView::MouseUp(BPoint where)
622{
623	BView::MouseUp(where);
624	_PerformMouseUp(where);
625
626	delete fDragRunner;
627	fDragRunner = NULL;
628}
629
630
631void
632BTextView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
633{
634	// check if it's a "click'n'move"
635	if (_PerformMouseMoved(where, code))
636		return;
637
638	switch (code) {
639		case B_ENTERED_VIEW:
640		case B_INSIDE_VIEW:
641			_TrackMouse(where, dragMessage, true);
642			break;
643
644		case B_EXITED_VIEW:
645			_DragCaret(-1);
646			if (Window()->IsActive() && dragMessage == NULL)
647				SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
648			break;
649
650		default:
651			BView::MouseMoved(where, code, dragMessage);
652	}
653}
654
655
656void
657BTextView::WindowActivated(bool active)
658{
659	BView::WindowActivated(active);
660
661	if (active && IsFocus()) {
662		if (!fActive)
663			_Activate();
664	} else {
665		if (fActive)
666			_Deactivate();
667	}
668
669	BPoint where;
670	uint32 buttons;
671	GetMouse(&where, &buttons, false);
672
673	if (Bounds().Contains(where))
674		_TrackMouse(where, NULL);
675}
676
677
678void
679BTextView::KeyDown(const char* bytes, int32 numBytes)
680{
681	const char keyPressed = bytes[0];
682
683	if (!fEditable) {
684		// only arrow and page keys are allowed
685		// (no need to hide the cursor)
686		switch (keyPressed) {
687			case B_LEFT_ARROW:
688			case B_RIGHT_ARROW:
689			case B_UP_ARROW:
690			case B_DOWN_ARROW:
691				_HandleArrowKey(keyPressed);
692				break;
693
694			case B_HOME:
695			case B_END:
696			case B_PAGE_UP:
697			case B_PAGE_DOWN:
698				_HandlePageKey(keyPressed);
699				break;
700
701			default:
702				BView::KeyDown(bytes, numBytes);
703				break;
704		}
705
706		return;
707	}
708
709	// hide the cursor and caret
710	if (IsFocus())
711		be_app->ObscureCursor();
712	_HideCaret();
713
714	switch (keyPressed) {
715		case B_BACKSPACE:
716			_HandleBackspace();
717			break;
718
719		case B_LEFT_ARROW:
720		case B_RIGHT_ARROW:
721		case B_UP_ARROW:
722		case B_DOWN_ARROW:
723			_HandleArrowKey(keyPressed);
724			break;
725
726		case B_DELETE:
727			_HandleDelete();
728			break;
729
730		case B_HOME:
731		case B_END:
732		case B_PAGE_UP:
733		case B_PAGE_DOWN:
734			_HandlePageKey(keyPressed);
735			break;
736
737		case B_ESCAPE:
738		case B_INSERT:
739		case B_FUNCTION_KEY:
740			// ignore, pass it up to superclass
741			BView::KeyDown(bytes, numBytes);
742			break;
743
744		default:
745			// bail out if the character is not allowed
746			if (fDisallowedChars
747				&& fDisallowedChars->HasItem(
748					reinterpret_cast<void*>((uint32)keyPressed))) {
749				beep();
750				return;
751			}
752
753			_HandleAlphaKey(bytes, numBytes);
754			break;
755	}
756
757	// draw the caret
758	if (fSelStart == fSelEnd)
759		_ShowCaret();
760}
761
762
763void
764BTextView::Pulse()
765{
766	if (fActive && fEditable && fSelStart == fSelEnd) {
767		if (system_time() > (fCaretTime + 500000.0))
768			_InvertCaret();
769	}
770}
771
772
773void
774BTextView::FrameResized(float newWidth, float newHeight)
775{
776	BView::FrameResized(newWidth, newHeight);
777	_UpdateScrollbars();
778}
779
780
781void
782BTextView::MakeFocus(bool focus)
783{
784	BView::MakeFocus(focus);
785
786	if (focus && Window() != NULL && Window()->IsActive()) {
787		if (!fActive)
788			_Activate();
789	} else {
790		if (fActive)
791			_Deactivate();
792	}
793}
794
795
796void
797BTextView::MessageReceived(BMessage* message)
798{
799	// ToDo: block input if not editable (Andrew)
800
801	// was this message dropped?
802	if (message->WasDropped()) {
803		BPoint dropOffset;
804		BPoint dropPoint = message->DropPoint(&dropOffset);
805		ConvertFromScreen(&dropPoint);
806		ConvertFromScreen(&dropOffset);
807		if (!_MessageDropped(message, dropPoint, dropOffset))
808			BView::MessageReceived(message);
809
810		return;
811	}
812
813	switch (message->what) {
814		case B_CUT:
815			if (!IsTypingHidden())
816				Cut(be_clipboard);
817			else
818				beep();
819			break;
820
821		case B_COPY:
822			if (!IsTypingHidden())
823				Copy(be_clipboard);
824			else
825				beep();
826			break;
827
828		case B_PASTE:
829			Paste(be_clipboard);
830			break;
831
832		case B_UNDO:
833			Undo(be_clipboard);
834			break;
835
836		case B_SELECT_ALL:
837			SelectAll();
838			break;
839
840		case B_INPUT_METHOD_EVENT:
841		{
842			int32 opcode;
843			if (message->FindInt32("be:opcode", &opcode) == B_OK) {
844				switch (opcode) {
845					case B_INPUT_METHOD_STARTED:
846					{
847						BMessenger messenger;
848						if (message->FindMessenger("be:reply_to", &messenger)
849								== B_OK) {
850							ASSERT(fInline == NULL);
851							fInline = new InlineInput(messenger);
852						}
853						break;
854					}
855
856					case B_INPUT_METHOD_STOPPED:
857						delete fInline;
858						fInline = NULL;
859						break;
860
861					case B_INPUT_METHOD_CHANGED:
862						if (fInline != NULL)
863							_HandleInputMethodChanged(message);
864						break;
865
866					case B_INPUT_METHOD_LOCATION_REQUEST:
867						if (fInline != NULL)
868							_HandleInputMethodLocationRequest();
869						break;
870
871					default:
872						break;
873				}
874			}
875			break;
876		}
877
878		case B_SET_PROPERTY:
879		case B_GET_PROPERTY:
880		case B_COUNT_PROPERTIES:
881		{
882			BPropertyInfo propInfo(sPropertyList);
883			BMessage specifier;
884			const char* property;
885
886			if (message->GetCurrentSpecifier(NULL, &specifier) < B_OK
887				|| specifier.FindString("property", &property) < B_OK) {
888				BView::MessageReceived(message);
889				return;
890			}
891
892			if (propInfo.FindMatch(message, 0, &specifier, specifier.what,
893					property) < B_OK) {
894				BView::MessageReceived(message);
895				break;
896			}
897
898			BMessage reply;
899			bool handled = false;
900			switch(message->what) {
901				case B_GET_PROPERTY:
902					handled = _GetProperty(&specifier, specifier.what, property,
903						&reply);
904					break;
905
906				case B_SET_PROPERTY:
907					handled = _SetProperty(&specifier, specifier.what, property,
908						&reply);
909					break;
910
911				case B_COUNT_PROPERTIES:
912					handled = _CountProperties(&specifier, specifier.what,
913						property, &reply);
914					break;
915
916				default:
917					break;
918			}
919			if (handled)
920				message->SendReply(&reply);
921			else
922				BView::MessageReceived(message);
923			break;
924		}
925
926		case _PING_:
927		{
928			if (message->HasInt64("clickTime")) {
929				bigtime_t clickTime;
930				message->FindInt64("clickTime", &clickTime);
931				if (clickTime == fClickTime) {
932					if (fSelStart != fSelEnd && fSelectable) {
933						BRegion region;
934						GetTextRegion(fSelStart, fSelEnd, &region);
935						if (region.Contains(fWhere))
936							_TrackMouse(fWhere, NULL);
937					}
938					delete fClickRunner;
939					fClickRunner = NULL;
940				}
941			} else if (fTrackingMouse) {
942				fTrackingMouse->SimulateMouseMovement(this);
943				_PerformAutoScrolling();
944			}
945			break;
946		}
947
948		case _DISPOSE_DRAG_:
949			if (fEditable)
950				_TrackDrag(fWhere);
951			break;
952
953		case kMsgNavigateArrow:
954		{
955			int32 key = message->GetInt32("key", 0);
956			int32 modifiers = message->GetInt32("modifiers", 0);
957			_HandleArrowKey(key, modifiers);
958			break;
959		}
960
961		case kMsgNavigatePage:
962		{
963			int32 key = message->GetInt32("key", 0);
964			int32 modifiers = message->GetInt32("modifiers", 0);
965			_HandlePageKey(key, modifiers);
966			break;
967		}
968
969		case kMsgRemoveWord:
970		{
971			int32 key = message->GetInt32("key", 0);
972			int32 modifiers = message->GetInt32("modifiers", 0);
973			if (key == B_DELETE)
974				_HandleDelete(modifiers);
975			else if (key == B_BACKSPACE)
976				_HandleBackspace(modifiers);
977			break;
978		}
979
980		default:
981			BView::MessageReceived(message);
982			break;
983	}
984}
985
986
987BHandler*
988BTextView::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
989	int32 what, const char* property)
990{
991	BPropertyInfo propInfo(sPropertyList);
992	BHandler* target = this;
993
994	if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) {
995		target = BView::ResolveSpecifier(message, index, specifier, what,
996			property);
997	}
998
999	return target;
1000}
1001
1002
1003status_t
1004BTextView::GetSupportedSuites(BMessage* data)
1005{
1006	if (data == NULL)
1007		return B_BAD_VALUE;
1008
1009	status_t err = data->AddString("suites", "suite/vnd.Be-text-view");
1010	if (err != B_OK)
1011		return err;
1012
1013	BPropertyInfo prop_info(sPropertyList);
1014	err = data->AddFlat("messages", &prop_info);
1015
1016	if (err != B_OK)
1017		return err;
1018	return BView::GetSupportedSuites(data);
1019}
1020
1021
1022status_t
1023BTextView::Perform(perform_code code, void* _data)
1024{
1025	switch (code) {
1026		case PERFORM_CODE_MIN_SIZE:
1027			((perform_data_min_size*)_data)->return_value
1028				= BTextView::MinSize();
1029			return B_OK;
1030		case PERFORM_CODE_MAX_SIZE:
1031			((perform_data_max_size*)_data)->return_value
1032				= BTextView::MaxSize();
1033			return B_OK;
1034		case PERFORM_CODE_PREFERRED_SIZE:
1035			((perform_data_preferred_size*)_data)->return_value
1036				= BTextView::PreferredSize();
1037			return B_OK;
1038		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1039			((perform_data_layout_alignment*)_data)->return_value
1040				= BTextView::LayoutAlignment();
1041			return B_OK;
1042		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1043			((perform_data_has_height_for_width*)_data)->return_value
1044				= BTextView::HasHeightForWidth();
1045			return B_OK;
1046		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1047		{
1048			perform_data_get_height_for_width* data
1049				= (perform_data_get_height_for_width*)_data;
1050			BTextView::GetHeightForWidth(data->width, &data->min, &data->max,
1051				&data->preferred);
1052			return B_OK;
1053		}
1054		case PERFORM_CODE_SET_LAYOUT:
1055		{
1056			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1057			BTextView::SetLayout(data->layout);
1058			return B_OK;
1059		}
1060		case PERFORM_CODE_LAYOUT_INVALIDATED:
1061		{
1062			perform_data_layout_invalidated* data
1063				= (perform_data_layout_invalidated*)_data;
1064			BTextView::LayoutInvalidated(data->descendants);
1065			return B_OK;
1066		}
1067		case PERFORM_CODE_DO_LAYOUT:
1068		{
1069			BTextView::DoLayout();
1070			return B_OK;
1071		}
1072	}
1073
1074	return BView::Perform(code, _data);
1075}
1076
1077
1078void
1079BTextView::SetText(const char* text, const text_run_array* runs)
1080{
1081	SetText(text, text ? strlen(text) : 0, runs);
1082}
1083
1084
1085void
1086BTextView::SetText(const char* text, int32 length, const text_run_array* runs)
1087{
1088	_CancelInputMethod();
1089
1090	// hide the caret/unhighlight the selection
1091	if (fActive) {
1092		if (fSelStart != fSelEnd) {
1093			if (fSelectable)
1094				Highlight(fSelStart, fSelEnd);
1095		} else
1096			_HideCaret();
1097	}
1098
1099	// remove data from buffer
1100	if (fText->Length() > 0)
1101		DeleteText(0, fText->Length());
1102
1103	if (text != NULL && length > 0)
1104		InsertText(text, length, 0, runs);
1105
1106	// recalculate line breaks and draw the text
1107	_Refresh(0, length, false);
1108	fCaretOffset = fSelStart = fSelEnd = 0;
1109	ScrollTo(B_ORIGIN);
1110
1111	// draw the caret
1112	_ShowCaret();
1113}
1114
1115
1116void
1117BTextView::SetText(BFile* file, int32 offset, int32 length,
1118	const text_run_array* runs)
1119{
1120	CALLED();
1121
1122	_CancelInputMethod();
1123
1124	if (file == NULL)
1125		return;
1126
1127	if (fText->Length() > 0)
1128		DeleteText(0, fText->Length());
1129
1130	if (!fText->InsertText(file, offset, length, 0))
1131		return;
1132
1133	// update the start offsets of each line below offset
1134	fLines->BumpOffset(length, _LineAt(offset) + 1);
1135
1136	// update the style runs
1137	fStyles->BumpOffset(length, fStyles->OffsetToRun(offset - 1) + 1);
1138
1139	if (fStylable && runs != NULL)
1140		SetRunArray(offset, offset + length, runs);
1141	else {
1142		// apply null-style to inserted text
1143		_ApplyStyleRange(offset, offset + length);
1144	}
1145
1146	// recalculate line breaks and draw the text
1147	_Refresh(0, length, false);
1148	fCaretOffset = fSelStart = fSelEnd = 0;
1149	ScrollToOffset(fSelStart);
1150
1151	// draw the caret
1152	_ShowCaret();
1153}
1154
1155
1156void
1157BTextView::Insert(const char* text, const text_run_array* runs)
1158{
1159	if (text != NULL)
1160		_DoInsertText(text, strlen(text), fSelStart, runs);
1161}
1162
1163
1164void
1165BTextView::Insert(const char* text, int32 length, const text_run_array* runs)
1166{
1167	if (text != NULL && length > 0)
1168		_DoInsertText(text, strnlen(text, length), fSelStart, runs);
1169}
1170
1171
1172void
1173BTextView::Insert(int32 offset, const char* text, int32 length,
1174	const text_run_array* runs)
1175{
1176	// pin offset at reasonable values
1177	if (offset < 0)
1178		offset = 0;
1179	else if (offset > fText->Length())
1180		offset = fText->Length();
1181
1182	if (text != NULL && length > 0)
1183		_DoInsertText(text, strnlen(text, length), offset, runs);
1184}
1185
1186
1187void
1188BTextView::Delete()
1189{
1190	Delete(fSelStart, fSelEnd);
1191}
1192
1193
1194void
1195BTextView::Delete(int32 startOffset, int32 endOffset)
1196{
1197	CALLED();
1198
1199	// pin offsets at reasonable values
1200	if (startOffset < 0)
1201		startOffset = 0;
1202	else if (startOffset > fText->Length())
1203		startOffset = fText->Length();
1204	if (endOffset < 0)
1205		endOffset = 0;
1206	else if (endOffset > fText->Length())
1207		endOffset = fText->Length();
1208
1209	// anything to delete?
1210	if (startOffset == endOffset)
1211		return;
1212
1213	// hide the caret/unhighlight the selection
1214	if (fActive) {
1215		if (fSelStart != fSelEnd) {
1216			if (fSelectable)
1217				Highlight(fSelStart, fSelEnd);
1218		} else
1219			_HideCaret();
1220	}
1221	// remove data from buffer
1222	DeleteText(startOffset, endOffset);
1223
1224	// check if the caret needs to be moved
1225	if (fCaretOffset >= endOffset)
1226		fCaretOffset -= (endOffset - startOffset);
1227	else if (fCaretOffset >= startOffset && fCaretOffset < endOffset)
1228		fCaretOffset = startOffset;
1229
1230	fSelEnd = fSelStart = fCaretOffset;
1231
1232	// recalculate line breaks and draw what's left
1233	_Refresh(startOffset, endOffset, false);
1234
1235	// draw the caret
1236	_ShowCaret();
1237}
1238
1239
1240const char*
1241BTextView::Text() const
1242{
1243	return fText->RealText();
1244}
1245
1246
1247int32
1248BTextView::TextLength() const
1249{
1250	return fText->Length();
1251}
1252
1253
1254void
1255BTextView::GetText(int32 offset, int32 length, char* buffer) const
1256{
1257	if (buffer != NULL)
1258		fText->GetString(offset, length, buffer);
1259}
1260
1261
1262uchar
1263BTextView::ByteAt(int32 offset) const
1264{
1265	if (offset < 0 || offset >= fText->Length())
1266		return '\0';
1267
1268	return fText->RealCharAt(offset);
1269}
1270
1271
1272int32
1273BTextView::CountLines() const
1274{
1275	return fLines->NumLines();
1276}
1277
1278
1279int32
1280BTextView::CurrentLine() const
1281{
1282	return LineAt(fSelStart);
1283}
1284
1285
1286void
1287BTextView::GoToLine(int32 index)
1288{
1289	_CancelInputMethod();
1290	_HideCaret();
1291	fSelStart = fSelEnd = fCaretOffset = OffsetAt(index);
1292	_ShowCaret();
1293}
1294
1295
1296void
1297BTextView::Cut(BClipboard* clipboard)
1298{
1299	_CancelInputMethod();
1300	if (!fEditable)
1301		return;
1302	if (fUndo) {
1303		delete fUndo;
1304		fUndo = new CutUndoBuffer(this);
1305	}
1306	Copy(clipboard);
1307	Delete();
1308}
1309
1310
1311void
1312BTextView::Copy(BClipboard* clipboard)
1313{
1314	_CancelInputMethod();
1315
1316	if (clipboard->Lock()) {
1317		clipboard->Clear();
1318
1319		BMessage* clip = clipboard->Data();
1320		if (clip != NULL) {
1321			int32 numBytes = fSelEnd - fSelStart;
1322			const char* text = fText->GetString(fSelStart, &numBytes);
1323			clip->AddData("text/plain", B_MIME_TYPE, text, numBytes);
1324
1325			int32 size;
1326			if (fStylable) {
1327				text_run_array* runArray = RunArray(fSelStart, fSelEnd, &size);
1328				clip->AddData("application/x-vnd.Be-text_run_array",
1329					B_MIME_TYPE, runArray, size);
1330				FreeRunArray(runArray);
1331			}
1332			clipboard->Commit();
1333		}
1334		clipboard->Unlock();
1335	}
1336}
1337
1338
1339void
1340BTextView::Paste(BClipboard* clipboard)
1341{
1342	CALLED();
1343	_CancelInputMethod();
1344
1345	if (!fEditable || !clipboard->Lock())
1346		return;
1347
1348	BMessage* clip = clipboard->Data();
1349	if (clip != NULL) {
1350		const char* text = NULL;
1351		ssize_t length = 0;
1352
1353		if (clip->FindData("text/plain", B_MIME_TYPE,
1354				(const void**)&text, &length) == B_OK) {
1355			text_run_array* runArray = NULL;
1356			ssize_t runLength = 0;
1357
1358			if (fStylable) {
1359				clip->FindData("application/x-vnd.Be-text_run_array",
1360					B_MIME_TYPE, (const void**)&runArray, &runLength);
1361			}
1362
1363			_FilterDisallowedChars((char*)text, length, runArray);
1364
1365			if (length < 1) {
1366				beep();
1367				clipboard->Unlock();
1368				return;
1369			}
1370
1371			if (fUndo) {
1372				delete fUndo;
1373				fUndo = new PasteUndoBuffer(this, text, length, runArray,
1374					runLength);
1375			}
1376
1377			if (fSelStart != fSelEnd)
1378				Delete();
1379
1380			Insert(text, length, runArray);
1381			ScrollToOffset(fSelEnd);
1382		}
1383	}
1384
1385	clipboard->Unlock();
1386}
1387
1388
1389void
1390BTextView::Clear()
1391{
1392	// We always check for fUndo != NULL (not only here),
1393	// because when fUndo is NULL, undo is deactivated.
1394	if (fUndo) {
1395		delete fUndo;
1396		fUndo = new ClearUndoBuffer(this);
1397	}
1398
1399	Delete();
1400}
1401
1402
1403bool
1404BTextView::AcceptsPaste(BClipboard* clipboard)
1405{
1406	bool result = false;
1407
1408	if (fEditable && clipboard && clipboard->Lock()) {
1409		BMessage* data = clipboard->Data();
1410		result = data && data->HasData("text/plain", B_MIME_TYPE);
1411		clipboard->Unlock();
1412	}
1413
1414	return result;
1415}
1416
1417
1418bool
1419BTextView::AcceptsDrop(const BMessage* message)
1420{
1421	return fEditable && message
1422		&& message->HasData("text/plain", B_MIME_TYPE);
1423}
1424
1425
1426void
1427BTextView::Select(int32 startOffset, int32 endOffset)
1428{
1429	CALLED();
1430	if (!fSelectable)
1431		return;
1432
1433	_CancelInputMethod();
1434
1435	// pin offsets at reasonable values
1436	if (startOffset < 0)
1437		startOffset = 0;
1438	else if (startOffset > fText->Length())
1439		startOffset = fText->Length();
1440	if (endOffset < 0)
1441		endOffset = 0;
1442	else if (endOffset > fText->Length())
1443		endOffset = fText->Length();
1444
1445	// a negative selection?
1446	if (startOffset > endOffset)
1447		return;
1448
1449	// is the new selection any different from the current selection?
1450	if (startOffset == fSelStart && endOffset == fSelEnd)
1451		return;
1452
1453	fStyles->InvalidateNullStyle();
1454
1455	_HideCaret();
1456
1457	if (startOffset == endOffset) {
1458		if (fSelStart != fSelEnd) {
1459			// unhilite the selection
1460			if (fActive)
1461				Highlight(fSelStart, fSelEnd);
1462		}
1463		fSelStart = fSelEnd = fCaretOffset = startOffset;
1464		_ShowCaret();
1465	} else {
1466		if (fActive) {
1467			// draw only those ranges that are different
1468			long start, end;
1469			if (startOffset != fSelStart) {
1470				// start of selection has changed
1471				if (startOffset > fSelStart) {
1472					start = fSelStart;
1473					end = startOffset;
1474				} else {
1475					start = startOffset;
1476					end = fSelStart;
1477				}
1478				Highlight(start, end);
1479			}
1480
1481			if (endOffset != fSelEnd) {
1482				// end of selection has changed
1483				if (endOffset > fSelEnd) {
1484					start = fSelEnd;
1485					end = endOffset;
1486				} else {
1487					start = endOffset;
1488					end = fSelEnd;
1489				}
1490				Highlight(start, end);
1491			}
1492		}
1493		fSelStart = startOffset;
1494		fSelEnd = endOffset;
1495	}
1496}
1497
1498
1499void
1500BTextView::SelectAll()
1501{
1502	Select(0, fText->Length());
1503}
1504
1505
1506void
1507BTextView::GetSelection(int32* _start, int32* _end) const
1508{
1509	int32 start = 0;
1510	int32 end = 0;
1511
1512	if (fSelectable) {
1513		start = fSelStart;
1514		end = fSelEnd;
1515	}
1516
1517	if (_start)
1518		*_start = start;
1519
1520	if (_end)
1521		*_end = end;
1522}
1523
1524
1525void
1526BTextView::SetFontAndColor(const BFont* font, uint32 mode,
1527	const rgb_color* color)
1528{
1529	SetFontAndColor(fSelStart, fSelEnd, font, mode, color);
1530}
1531
1532
1533void
1534BTextView::SetFontAndColor(int32 startOffset, int32 endOffset,
1535	const BFont* font, uint32 mode, const rgb_color* color)
1536{
1537	CALLED();
1538
1539	_HideCaret();
1540
1541	const int32 textLength = fText->Length();
1542
1543	if (!fStylable) {
1544		// When the text view is not stylable, we always set the whole text's
1545		// style and ignore the offsets
1546		startOffset = 0;
1547		endOffset = textLength;
1548	} else {
1549		// pin offsets at reasonable values
1550		if (startOffset < 0)
1551			startOffset = 0;
1552		else if (startOffset > textLength)
1553			startOffset = textLength;
1554
1555		if (endOffset < 0)
1556			endOffset = 0;
1557		else if (endOffset > textLength)
1558			endOffset = textLength;
1559	}
1560
1561	// apply the style to the style buffer
1562	fStyles->InvalidateNullStyle();
1563	_ApplyStyleRange(startOffset, endOffset, mode, font, color);
1564
1565	if ((mode & (B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE)) != 0) {
1566		// ToDo: maybe only invalidate the layout (depending on
1567		// B_SUPPORTS_LAYOUT) and have it _Refresh() automatically?
1568		InvalidateLayout();
1569		// recalc the line breaks and redraw with new style
1570		_Refresh(startOffset, endOffset, false);
1571	} else {
1572		// the line breaks wont change, simply redraw
1573		_RequestDrawLines(_LineAt(startOffset), _LineAt(endOffset));
1574	}
1575
1576	_ShowCaret();
1577}
1578
1579
1580void
1581BTextView::GetFontAndColor(int32 offset, BFont* _font,
1582	rgb_color* _color) const
1583{
1584	fStyles->GetStyle(offset, _font, _color);
1585}
1586
1587
1588void
1589BTextView::GetFontAndColor(BFont* _font, uint32* _mode,
1590	rgb_color* _color, bool* _sameColor) const
1591{
1592	fStyles->ContinuousGetStyle(_font, _mode, _color, _sameColor,
1593		fSelStart, fSelEnd);
1594}
1595
1596
1597void
1598BTextView::SetRunArray(int32 startOffset, int32 endOffset,
1599	const text_run_array* runs)
1600{
1601	CALLED();
1602
1603	_CancelInputMethod();
1604
1605	text_run_array oneRun;
1606
1607	if (!fStylable) {
1608		// when the text view is not stylable, we always set the whole text's
1609		// style with the first run and ignore the offsets
1610		if (runs->count == 0)
1611			return;
1612
1613		startOffset = 0;
1614		endOffset = fText->Length();
1615		oneRun.count = 1;
1616		oneRun.runs[0] = runs->runs[0];
1617		oneRun.runs[0].offset = 0;
1618		runs = &oneRun;
1619	} else {
1620		// pin offsets at reasonable values
1621		if (startOffset < 0)
1622			startOffset = 0;
1623		else if (startOffset > fText->Length())
1624			startOffset = fText->Length();
1625
1626		if (endOffset < 0)
1627			endOffset = 0;
1628		else if (endOffset > fText->Length())
1629			endOffset = fText->Length();
1630	}
1631
1632	_SetRunArray(startOffset, endOffset, runs);
1633
1634	_Refresh(startOffset, endOffset, false);
1635}
1636
1637
1638text_run_array*
1639BTextView::RunArray(int32 startOffset, int32 endOffset, int32* _size) const
1640{
1641	// pin offsets at reasonable values
1642	if (startOffset < 0)
1643		startOffset = 0;
1644	else if (startOffset > fText->Length())
1645		startOffset = fText->Length();
1646
1647	if (endOffset < 0)
1648		endOffset = 0;
1649	else if (endOffset > fText->Length())
1650		endOffset = fText->Length();
1651
1652	STEStyleRange* styleRange
1653		= fStyles->GetStyleRange(startOffset, endOffset - 1);
1654	if (styleRange == NULL)
1655		return NULL;
1656
1657	text_run_array* runArray = AllocRunArray(styleRange->count, _size);
1658	if (runArray != NULL) {
1659		for (int32 i = 0; i < runArray->count; i++) {
1660			runArray->runs[i].offset = styleRange->runs[i].offset;
1661			runArray->runs[i].font = styleRange->runs[i].style.font;
1662			runArray->runs[i].color = styleRange->runs[i].style.color;
1663		}
1664	}
1665
1666	free(styleRange);
1667
1668	return runArray;
1669}
1670
1671
1672int32
1673BTextView::LineAt(int32 offset) const
1674{
1675	// pin offset at reasonable values
1676	if (offset < 0)
1677		offset = 0;
1678	else if (offset > fText->Length())
1679		offset = fText->Length();
1680
1681	int32 lineNum = _LineAt(offset);
1682	if (_IsOnEmptyLastLine(offset))
1683		lineNum++;
1684	return lineNum;
1685}
1686
1687
1688int32
1689BTextView::LineAt(BPoint point) const
1690{
1691	int32 lineNum = _LineAt(point);
1692	if ((*fLines)[lineNum + 1]->origin <= point.y - fTextRect.top)
1693		lineNum++;
1694
1695	return lineNum;
1696}
1697
1698
1699BPoint
1700BTextView::PointAt(int32 offset, float* _height) const
1701{
1702	// pin offset at reasonable values
1703	if (offset < 0)
1704		offset = 0;
1705	else if (offset > fText->Length())
1706		offset = fText->Length();
1707
1708	// ToDo: Cleanup.
1709	int32 lineNum = _LineAt(offset);
1710	STELine* line = (*fLines)[lineNum];
1711	float height = 0;
1712
1713	BPoint result;
1714	result.x = 0.0;
1715	result.y = line->origin + fTextRect.top;
1716
1717	bool onEmptyLastLine = _IsOnEmptyLastLine(offset);
1718
1719	if (fStyles->NumRuns() == 0) {
1720		// Handle the case where there is only one line (no text inserted)
1721		fStyles->SyncNullStyle(0);
1722		height = _NullStyleHeight();
1723	} else {
1724		height = (line + 1)->origin - line->origin;
1725
1726		if (onEmptyLastLine) {
1727			// special case: go down one line if offset is at the newline
1728			// at the end of the buffer ...
1729			result.y += height;
1730			// ... and return the height of that (empty) line
1731			fStyles->SyncNullStyle(offset);
1732			height = _NullStyleHeight();
1733		} else {
1734			int32 length = offset - line->offset;
1735			result.x += _TabExpandedStyledWidth(line->offset, length);
1736		}
1737	}
1738
1739	if (fAlignment != B_ALIGN_LEFT) {
1740		float lineWidth = onEmptyLastLine ? 0.0 : LineWidth(lineNum);
1741		float alignmentOffset = fTextRect.Width() - lineWidth;
1742		if (fAlignment == B_ALIGN_CENTER)
1743			alignmentOffset /= 2;
1744		result.x += alignmentOffset;
1745	}
1746
1747	// convert from text rect coordinates
1748	result.x += fTextRect.left;
1749
1750	// round up
1751	result.x = lroundf(result.x);
1752	result.y = lroundf(result.y);
1753	if (_height != NULL)
1754		*_height = height;
1755
1756	return result;
1757}
1758
1759
1760int32
1761BTextView::OffsetAt(BPoint point) const
1762{
1763	const int32 textLength = fText->Length();
1764
1765	// should we even bother?
1766	if (point.y >= fTextRect.bottom)
1767		return textLength;
1768	else if (point.y < fTextRect.top)
1769		return 0;
1770
1771	int32 lineNum = _LineAt(point);
1772	STELine* line = (*fLines)[lineNum];
1773
1774#define COMPILE_PROBABLY_BAD_CODE 1
1775
1776#if COMPILE_PROBABLY_BAD_CODE
1777	// special case: if point is within the text rect and PixelToLine()
1778	// tells us that it's on the last line, but if point is actually
1779	// lower than the bottom of the last line, return the last offset
1780	// (can happen for newlines)
1781	if (lineNum == (fLines->NumLines() - 1)) {
1782		if (point.y >= ((line + 1)->origin + fTextRect.top))
1783			return textLength;
1784	}
1785#endif
1786
1787	// convert to text rect coordinates
1788	if (fAlignment != B_ALIGN_LEFT) {
1789		float alignmentOffset = fTextRect.Width() - LineWidth(lineNum);
1790		if (fAlignment == B_ALIGN_CENTER)
1791			alignmentOffset /= 2;
1792		point.x -= alignmentOffset;
1793	}
1794
1795	point.x -= fTextRect.left;
1796	point.x = max_c(point.x, 0.0);
1797
1798	// ToDo: The following code isn't very efficient, because it always starts
1799	// from the left end, so when the point is near the right end it's very
1800	// slow.
1801	int32 offset = line->offset;
1802	const int32 limit = (line + 1)->offset;
1803	float location = 0;
1804	do {
1805		const int32 nextInitial = _NextInitialByte(offset);
1806		const int32 saveOffset = offset;
1807		float width = 0;
1808		if (ByteAt(offset) == B_TAB)
1809			width = _ActualTabWidth(location);
1810		else
1811			width = _StyledWidth(saveOffset, nextInitial - saveOffset);
1812		if (location + width > point.x) {
1813			if (fabs(location + width - point.x) < fabs(location - point.x))
1814				offset = nextInitial;
1815			break;
1816		}
1817
1818		location += width;
1819		offset = nextInitial;
1820	} while (offset < limit);
1821
1822	if (offset == (line + 1)->offset) {
1823		// special case: newlines aren't visible
1824		// return the offset of the character preceding the newline
1825		if (ByteAt(offset - 1) == B_ENTER)
1826			return --offset;
1827
1828		// special case: return the offset preceding any spaces that
1829		// aren't at the end of the buffer
1830		if (offset != textLength && ByteAt(offset - 1) == B_SPACE)
1831			return --offset;
1832	}
1833
1834	return offset;
1835}
1836
1837
1838int32
1839BTextView::OffsetAt(int32 line) const
1840{
1841	if (line < 0)
1842		return 0;
1843
1844	if (line > fLines->NumLines())
1845		return fText->Length();
1846
1847	return (*fLines)[line]->offset;
1848}
1849
1850
1851void
1852BTextView::FindWord(int32 offset, int32* _fromOffset, int32* _toOffset)
1853{
1854	if (offset < 0) {
1855		if (_fromOffset)
1856			*_fromOffset = 0;
1857
1858		if (_toOffset)
1859			*_toOffset = 0;
1860
1861		return;
1862	}
1863
1864	if (offset > fText->Length()) {
1865		if (_fromOffset)
1866			*_fromOffset = fText->Length();
1867
1868		if (_toOffset)
1869			*_toOffset = fText->Length();
1870
1871		return;
1872	}
1873
1874	if (_fromOffset)
1875		*_fromOffset = _PreviousWordBoundary(offset);
1876
1877	if (_toOffset)
1878		*_toOffset = _NextWordBoundary(offset);
1879}
1880
1881
1882bool
1883BTextView::CanEndLine(int32 offset)
1884{
1885	if (offset < 0 || offset > fText->Length())
1886		return false;
1887
1888	// TODO: This should be improved using the LocaleKit.
1889	uint32 classification = _CharClassification(offset);
1890
1891	// wrapping is always allowed at end of text and at newlines
1892	if (classification == CHAR_CLASS_END_OF_TEXT || ByteAt(offset) == B_ENTER)
1893		return true;
1894
1895	uint32 nextClassification = _CharClassification(offset + 1);
1896	if (nextClassification == CHAR_CLASS_END_OF_TEXT)
1897		return true;
1898
1899	// never separate a punctuation char from its preceeding word
1900	if (classification == CHAR_CLASS_DEFAULT
1901		&& nextClassification == CHAR_CLASS_PUNCTUATION) {
1902		return false;
1903	}
1904
1905	if ((classification == CHAR_CLASS_WHITESPACE
1906			&& nextClassification != CHAR_CLASS_WHITESPACE)
1907		|| (classification != CHAR_CLASS_WHITESPACE
1908			&& nextClassification == CHAR_CLASS_WHITESPACE)) {
1909		return true;
1910	}
1911
1912	// allow wrapping after whitespace, unless more whitespace (except for
1913	// newline) follows
1914	if (classification == CHAR_CLASS_WHITESPACE
1915		&& (nextClassification != CHAR_CLASS_WHITESPACE
1916			|| ByteAt(offset + 1) == B_ENTER)) {
1917		return true;
1918	}
1919
1920	// allow wrapping after punctuation chars, unless more punctuation, closing
1921	// parens or quotes follow
1922	if (classification == CHAR_CLASS_PUNCTUATION
1923		&& nextClassification != CHAR_CLASS_PUNCTUATION
1924		&& nextClassification != CHAR_CLASS_PARENS_CLOSE
1925		&& nextClassification != CHAR_CLASS_QUOTE) {
1926		return true;
1927	}
1928
1929	// allow wrapping after quotes, graphical chars and closing parens only if
1930	// whitespace follows (not perfect, but seems to do the right thing most
1931	// of the time)
1932	if ((classification == CHAR_CLASS_QUOTE
1933			|| classification == CHAR_CLASS_GRAPHICAL
1934			|| classification == CHAR_CLASS_PARENS_CLOSE)
1935		&& nextClassification == CHAR_CLASS_WHITESPACE) {
1936		return true;
1937	}
1938
1939	return false;
1940}
1941
1942
1943float
1944BTextView::LineWidth(int32 lineNumber) const
1945{
1946	if (lineNumber < 0 || lineNumber >= fLines->NumLines())
1947		return 0;
1948
1949	STELine* line = (*fLines)[lineNumber];
1950	int32 length = (line + 1)->offset - line->offset;
1951
1952	// skip newline at the end of the line, if any, as it does no contribute
1953	// to the width
1954	if (ByteAt((line + 1)->offset - 1) == B_ENTER)
1955		length--;
1956
1957	return _TabExpandedStyledWidth(line->offset, length);
1958}
1959
1960
1961float
1962BTextView::LineHeight(int32 lineNumber) const
1963{
1964	float lineHeight = TextHeight(lineNumber, lineNumber);
1965	if (lineHeight == 0.0) {
1966		// We probably don't have text content yet. Take the initial
1967		// style's font height or fall back to the plain font.
1968		const BFont* font;
1969		fStyles->GetNullStyle(&font, NULL);
1970		if (font == NULL)
1971			font = be_plain_font;
1972
1973		font_height fontHeight;
1974		font->GetHeight(&fontHeight);
1975		// This is how the height is calculated in _RecalculateLineBreaks().
1976		lineHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 1;
1977	}
1978
1979	return lineHeight;
1980}
1981
1982
1983float
1984BTextView::TextHeight(int32 startLine, int32 endLine) const
1985{
1986	const int32 numLines = fLines->NumLines();
1987	if (startLine < 0)
1988		startLine = 0;
1989	else if (startLine > numLines - 1)
1990		startLine = numLines - 1;
1991
1992	if (endLine < 0)
1993		endLine = 0;
1994	else if (endLine > numLines - 1)
1995		endLine = numLines - 1;
1996
1997	float height = (*fLines)[endLine + 1]->origin
1998		- (*fLines)[startLine]->origin;
1999
2000	if (startLine != endLine && endLine == numLines - 1
2001		&& fText->RealCharAt(fText->Length() - 1) == B_ENTER) {
2002		height += (*fLines)[endLine + 1]->origin - (*fLines)[endLine]->origin;
2003	}
2004
2005	return ceilf(height);
2006}
2007
2008
2009void
2010BTextView::GetTextRegion(int32 startOffset, int32 endOffset,
2011	BRegion* outRegion) const
2012{
2013	if (!outRegion)
2014		return;
2015
2016	outRegion->MakeEmpty();
2017
2018	// pin offsets at reasonable values
2019	if (startOffset < 0)
2020		startOffset = 0;
2021	else if (startOffset > fText->Length())
2022		startOffset = fText->Length();
2023	if (endOffset < 0)
2024		endOffset = 0;
2025	else if (endOffset > fText->Length())
2026		endOffset = fText->Length();
2027
2028	// return an empty region if the range is invalid
2029	if (startOffset >= endOffset)
2030		return;
2031
2032	float startLineHeight = 0.0;
2033	float endLineHeight = 0.0;
2034	BPoint startPt = PointAt(startOffset, &startLineHeight);
2035	BPoint endPt = PointAt(endOffset, &endLineHeight);
2036
2037	startLineHeight = ceilf(startLineHeight);
2038	endLineHeight = ceilf(endLineHeight);
2039
2040	BRect selRect;
2041
2042	if (startPt.y == endPt.y) {
2043		// this is a one-line region
2044		selRect.left = max_c(startPt.x, fTextRect.left);
2045		selRect.top = startPt.y;
2046		selRect.right = endPt.x - 1.0;
2047		selRect.bottom = endPt.y + endLineHeight - 1.0;
2048		outRegion->Include(selRect);
2049	} else {
2050		// more than one line in the specified offset range
2051		selRect.left = max_c(startPt.x, fTextRect.left);
2052		selRect.top = startPt.y;
2053		selRect.right = fTextRect.right;
2054		selRect.bottom = startPt.y + startLineHeight - 1.0;
2055		outRegion->Include(selRect);
2056
2057		if (startPt.y + startLineHeight < endPt.y) {
2058			// more than two lines in the range
2059			selRect.left = fTextRect.left;
2060			selRect.top = startPt.y + startLineHeight;
2061			selRect.right = fTextRect.right;
2062			selRect.bottom = endPt.y - 1.0;
2063			outRegion->Include(selRect);
2064		}
2065
2066		selRect.left = fTextRect.left;
2067		selRect.top = endPt.y;
2068		selRect.right = endPt.x - 1.0;
2069		selRect.bottom = endPt.y + endLineHeight - 1.0;
2070		outRegion->Include(selRect);
2071	}
2072}
2073
2074
2075void
2076BTextView::ScrollToOffset(int32 offset)
2077{
2078	BRect bounds = Bounds();
2079	float lineHeight = 0.0;
2080	float xDiff = 0.0;
2081	float yDiff = 0.0;
2082	BPoint point = PointAt(offset, &lineHeight);
2083
2084	// horizontal
2085	float extraSpace = fAlignment == B_ALIGN_LEFT ?
2086		ceilf(bounds.IntegerWidth() / 2) : 0.0;
2087
2088	if (point.x < bounds.left)
2089		xDiff = point.x - bounds.left - extraSpace;
2090	else if (point.x > bounds.right)
2091		xDiff = point.x - bounds.right + extraSpace;
2092
2093	// vertical
2094	if (point.y < bounds.top)
2095		yDiff = point.y - bounds.top;
2096	else if (point.y + lineHeight > bounds.bottom
2097		&& point.y - lineHeight > bounds.top) {
2098		yDiff = point.y + lineHeight - bounds.bottom;
2099	}
2100
2101	// prevent negative scroll offset
2102	if (bounds.left + xDiff < 0.0)
2103		xDiff = -bounds.left;
2104	if (bounds.top + yDiff < 0.0)
2105		yDiff = -bounds.top;
2106
2107	ScrollBy(xDiff, yDiff);
2108}
2109
2110
2111void
2112BTextView::ScrollToSelection()
2113{
2114	ScrollToOffset(fSelStart);
2115}
2116
2117
2118void
2119BTextView::Highlight(int32 startOffset, int32 endOffset)
2120{
2121	// pin offsets at reasonable values
2122	if (startOffset < 0)
2123		startOffset = 0;
2124	else if (startOffset > fText->Length())
2125		startOffset = fText->Length();
2126	if (endOffset < 0)
2127		endOffset = 0;
2128	else if (endOffset > fText->Length())
2129		endOffset = fText->Length();
2130
2131	if (startOffset >= endOffset)
2132		return;
2133
2134	BRegion selRegion;
2135	GetTextRegion(startOffset, endOffset, &selRegion);
2136
2137	SetDrawingMode(B_OP_INVERT);
2138	FillRegion(&selRegion, B_SOLID_HIGH);
2139	SetDrawingMode(B_OP_COPY);
2140}
2141
2142
2143// #pragma mark - Configuration methods
2144
2145
2146void
2147BTextView::SetTextRect(BRect rect)
2148{
2149	if (rect == fTextRect)
2150		return;
2151
2152	if (!fWrap) {
2153		rect.right = Bounds().right - fLayoutData->rightInset;
2154		rect.bottom = Bounds().bottom - fLayoutData->bottomInset;
2155	}
2156
2157	fLayoutData->UpdateInsets(Bounds().OffsetToCopy(B_ORIGIN), rect);
2158
2159	_ResetTextRect();
2160}
2161
2162
2163BRect
2164BTextView::TextRect() const
2165{
2166	return fTextRect;
2167}
2168
2169
2170void
2171BTextView::_ResetTextRect()
2172{
2173	BRect oldTextRect(fTextRect);
2174	// reset text rect to bounds minus insets ...
2175	fTextRect = Bounds().OffsetToCopy(B_ORIGIN);
2176	fTextRect.left += fLayoutData->leftInset;
2177	fTextRect.top += fLayoutData->topInset;
2178	fTextRect.right -= fLayoutData->rightInset;
2179	fTextRect.bottom -= fLayoutData->bottomInset;
2180
2181	// and rewrap (potentially adjusting the right and the bottom of the text
2182	// rect)
2183	_Refresh(0, TextLength(), false);
2184
2185	// Make sure that the dirty area outside the text is redrawn too.
2186	BRegion invalid(oldTextRect | fTextRect);
2187	invalid.Exclude(fTextRect);
2188	Invalidate(&invalid);
2189}
2190
2191
2192void
2193BTextView::SetInsets(float left, float top, float right, float bottom)
2194{
2195	if (fLayoutData->leftInset == left
2196		&& fLayoutData->topInset == top
2197		&& fLayoutData->rightInset == right
2198		&& fLayoutData->bottomInset == bottom)
2199		return;
2200
2201	fLayoutData->leftInset = left;
2202	fLayoutData->topInset = top;
2203	fLayoutData->rightInset = right;
2204	fLayoutData->bottomInset = bottom;
2205
2206	InvalidateLayout();
2207	Invalidate();
2208}
2209
2210
2211void
2212BTextView::GetInsets(float* _left, float* _top, float* _right,
2213	float* _bottom) const
2214{
2215	if (_left)
2216		*_left = fLayoutData->leftInset;
2217	if (_top)
2218		*_top = fLayoutData->topInset;
2219	if (_right)
2220		*_right = fLayoutData->rightInset;
2221	if (_bottom)
2222		*_bottom = fLayoutData->bottomInset;
2223}
2224
2225
2226void
2227BTextView::SetStylable(bool stylable)
2228{
2229	fStylable = stylable;
2230}
2231
2232
2233bool
2234BTextView::IsStylable() const
2235{
2236	return fStylable;
2237}
2238
2239
2240void
2241BTextView::SetTabWidth(float width)
2242{
2243	if (width == fTabWidth)
2244		return;
2245
2246	fTabWidth = width;
2247
2248	if (Window() != NULL)
2249		_Refresh(0, fText->Length(), false);
2250}
2251
2252
2253float
2254BTextView::TabWidth() const
2255{
2256	return fTabWidth;
2257}
2258
2259
2260void
2261BTextView::MakeSelectable(bool selectable)
2262{
2263	if (selectable == fSelectable)
2264		return;
2265
2266	fSelectable = selectable;
2267
2268	if (fActive && fSelStart != fSelEnd && Window() != NULL)
2269		Highlight(fSelStart, fSelEnd);
2270}
2271
2272
2273bool
2274BTextView::IsSelectable() const
2275{
2276	return fSelectable;
2277}
2278
2279
2280void
2281BTextView::MakeEditable(bool editable)
2282{
2283	if (editable == fEditable)
2284		return;
2285
2286	fEditable = editable;
2287	// TextControls change the color of the text when
2288	// they are made editable, so we need to invalidate
2289	// the NULL style here
2290	// TODO: it works well, but it could be caused by a bug somewhere else
2291	if (fEditable)
2292		fStyles->InvalidateNullStyle();
2293	if (Window() != NULL && fActive) {
2294		if (!fEditable) {
2295			_HideCaret();
2296			_CancelInputMethod();
2297		}
2298	}
2299}
2300
2301
2302bool
2303BTextView::IsEditable() const
2304{
2305	return fEditable;
2306}
2307
2308
2309void
2310BTextView::SetWordWrap(bool wrap)
2311{
2312	if (wrap == fWrap)
2313		return;
2314
2315	bool updateOnScreen = fActive && Window() != NULL;
2316	if (updateOnScreen) {
2317		// hide the caret, unhilite the selection
2318		if (fSelStart != fSelEnd) {
2319			if (fSelectable)
2320				Highlight(fSelStart, fSelEnd);
2321		} else
2322			_HideCaret();
2323	}
2324
2325	fWrap = wrap;
2326	if (wrap)
2327		_ResetTextRect();
2328	_Refresh(0, fText->Length(), false);
2329
2330	if (updateOnScreen) {
2331		// show the caret, hilite the selection
2332		if (fSelStart != fSelEnd) {
2333			if (fSelectable)
2334				Highlight(fSelStart, fSelEnd);
2335		} else
2336			_ShowCaret();
2337	}
2338}
2339
2340
2341bool
2342BTextView::DoesWordWrap() const
2343{
2344	return fWrap;
2345}
2346
2347
2348void
2349BTextView::SetMaxBytes(int32 max)
2350{
2351	const int32 textLength = fText->Length();
2352	fMaxBytes = max;
2353
2354	if (fMaxBytes < textLength) {
2355		int32 offset = fMaxBytes;
2356		// Delete the text after fMaxBytes, but
2357		// respect multibyte characters boundaries.
2358		const int32 previousInitial = _PreviousInitialByte(offset);
2359		if (_NextInitialByte(previousInitial) != offset)
2360			offset = previousInitial;
2361
2362		Delete(offset, textLength);
2363	}
2364}
2365
2366
2367int32
2368BTextView::MaxBytes() const
2369{
2370	return fMaxBytes;
2371}
2372
2373
2374void
2375BTextView::DisallowChar(uint32 character)
2376{
2377	if (fDisallowedChars == NULL)
2378		fDisallowedChars = new BList;
2379	if (!fDisallowedChars->HasItem(reinterpret_cast<void*>(character)))
2380		fDisallowedChars->AddItem(reinterpret_cast<void*>(character));
2381}
2382
2383
2384void
2385BTextView::AllowChar(uint32 character)
2386{
2387	if (fDisallowedChars != NULL)
2388		fDisallowedChars->RemoveItem(reinterpret_cast<void*>(character));
2389}
2390
2391
2392void
2393BTextView::SetAlignment(alignment align)
2394{
2395	// Do a reality check
2396	if (fAlignment != align &&
2397			(align == B_ALIGN_LEFT ||
2398			 align == B_ALIGN_RIGHT ||
2399			 align == B_ALIGN_CENTER)) {
2400		fAlignment = align;
2401
2402		// After setting new alignment, update the view/window
2403		if (Window() != NULL)
2404			Invalidate();
2405	}
2406}
2407
2408
2409alignment
2410BTextView::Alignment() const
2411{
2412	return fAlignment;
2413}
2414
2415
2416void
2417BTextView::SetAutoindent(bool state)
2418{
2419	fAutoindent = state;
2420}
2421
2422
2423bool
2424BTextView::DoesAutoindent() const
2425{
2426	return fAutoindent;
2427}
2428
2429
2430void
2431BTextView::SetColorSpace(color_space colors)
2432{
2433	if (colors != fColorSpace && fOffscreen) {
2434		fColorSpace = colors;
2435		_DeleteOffscreen();
2436		_NewOffscreen();
2437	}
2438}
2439
2440
2441color_space
2442BTextView::ColorSpace() const
2443{
2444	return fColorSpace;
2445}
2446
2447
2448void
2449BTextView::MakeResizable(bool resize, BView* resizeView)
2450{
2451	if (resize) {
2452		fResizable = true;
2453		fContainerView = resizeView;
2454
2455		// Wrapping mode and resizable mode can't live together
2456		if (fWrap) {
2457			fWrap = false;
2458
2459			if (fActive && Window() != NULL) {
2460				if (fSelStart != fSelEnd) {
2461					if (fSelectable)
2462						Highlight(fSelStart, fSelEnd);
2463				} else
2464					_HideCaret();
2465			}
2466		}
2467		// We need to reset the right inset, as otherwise the auto-resize would
2468		// get confused about just how wide the textview needs to be.
2469		// This seems to be an artefact of how Tracker creates the textview
2470		// during a rename action.
2471		fLayoutData->rightInset = fLayoutData->leftInset;
2472	} else {
2473		fResizable = false;
2474		fContainerView = NULL;
2475		if (fOffscreen)
2476			_DeleteOffscreen();
2477		_NewOffscreen();
2478	}
2479
2480	_Refresh(0, fText->Length(), false);
2481}
2482
2483
2484bool
2485BTextView::IsResizable() const
2486{
2487	return fResizable;
2488}
2489
2490
2491void
2492BTextView::SetDoesUndo(bool undo)
2493{
2494	if (undo && fUndo == NULL)
2495		fUndo = new UndoBuffer(this, B_UNDO_UNAVAILABLE);
2496	else if (!undo && fUndo != NULL) {
2497		delete fUndo;
2498		fUndo = NULL;
2499	}
2500}
2501
2502
2503bool
2504BTextView::DoesUndo() const
2505{
2506	return fUndo != NULL;
2507}
2508
2509
2510void
2511BTextView::HideTyping(bool enabled)
2512{
2513	if (enabled)
2514		Delete(0, fText->Length());
2515
2516	fText->SetPasswordMode(enabled);
2517}
2518
2519
2520bool
2521BTextView::IsTypingHidden() const
2522{
2523	return fText->PasswordMode();
2524}
2525
2526
2527// #pragma mark - Size methods
2528
2529
2530void
2531BTextView::ResizeToPreferred()
2532{
2533	BView::ResizeToPreferred();
2534}
2535
2536
2537void
2538BTextView::GetPreferredSize(float* _width, float* _height)
2539{
2540	CALLED();
2541
2542	_ValidateLayoutData();
2543
2544	if (_width) {
2545		float width = Bounds().Width();
2546		if (width < fLayoutData->min.width
2547			|| (Flags() & B_SUPPORTS_LAYOUT) != 0) {
2548			width = fLayoutData->min.width;
2549		}
2550		*_width = width;
2551	}
2552
2553	if (_height) {
2554		float height = Bounds().Height();
2555		if (height < fLayoutData->min.height
2556			|| (Flags() & B_SUPPORTS_LAYOUT) != 0) {
2557			height = fLayoutData->min.height;
2558		}
2559		*_height = height;
2560	}
2561}
2562
2563
2564BSize
2565BTextView::MinSize()
2566{
2567	CALLED();
2568
2569	_ValidateLayoutData();
2570	return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
2571}
2572
2573
2574BSize
2575BTextView::MaxSize()
2576{
2577	CALLED();
2578
2579	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
2580		BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
2581}
2582
2583
2584BSize
2585BTextView::PreferredSize()
2586{
2587	CALLED();
2588
2589	_ValidateLayoutData();
2590	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
2591		fLayoutData->preferred);
2592}
2593
2594
2595bool
2596BTextView::HasHeightForWidth()
2597{
2598	if (IsEditable())
2599		return BView::HasHeightForWidth();
2600
2601	// When not editable, we assume that all text is supposed to be visible.
2602	return true;
2603}
2604
2605
2606void
2607BTextView::GetHeightForWidth(float width, float* min, float* max,
2608	float* preferred)
2609{
2610	if (IsEditable()) {
2611		BView::GetHeightForWidth(width, min, max, preferred);
2612		return;
2613	}
2614
2615	// TODO: don't change the actual text rect!
2616	fTextRect.right = fTextRect.left + width;
2617	_Refresh(0, TextLength(), false);
2618
2619	if (min != NULL)
2620		*min = fTextRect.Height();
2621	if (max != NULL)
2622		*max = B_SIZE_UNLIMITED;
2623	if (preferred != NULL)
2624		*preferred = fTextRect.Height();
2625}
2626
2627
2628//	#pragma mark - Layout methods
2629
2630
2631void
2632BTextView::LayoutInvalidated(bool descendants)
2633{
2634	CALLED();
2635
2636	fLayoutData->valid = false;
2637}
2638
2639
2640void
2641BTextView::DoLayout()
2642{
2643	// Bail out, if we shan't do layout.
2644	if (!(Flags() & B_SUPPORTS_LAYOUT))
2645		return;
2646
2647	CALLED();
2648
2649	// If the user set a layout, we let the base class version call its
2650	// hook.
2651	if (GetLayout()) {
2652		BView::DoLayout();
2653		return;
2654	}
2655
2656	_ValidateLayoutData();
2657
2658	// validate current size
2659	BSize size(Bounds().Size());
2660	if (size.width < fLayoutData->min.width)
2661		size.width = fLayoutData->min.width;
2662	if (size.height < fLayoutData->min.height)
2663		size.height = fLayoutData->min.height;
2664
2665	_ResetTextRect();
2666}
2667
2668
2669void
2670BTextView::_ValidateLayoutData()
2671{
2672	if (fLayoutData->valid)
2673		return;
2674
2675	CALLED();
2676
2677	float lineHeight = ceilf(LineHeight(0));
2678	TRACE("line height: %.2f\n", lineHeight);
2679
2680	// compute our minimal size
2681	BSize min(lineHeight * 3, lineHeight);
2682	min.width += fLayoutData->leftInset + fLayoutData->rightInset;
2683	min.height += fLayoutData->topInset + fLayoutData->bottomInset;
2684
2685	fLayoutData->min = min;
2686
2687	// compute our preferred size
2688	fLayoutData->preferred.height = fTextRect.Height()
2689		+ fLayoutData->topInset + fLayoutData->bottomInset;
2690
2691	if (fWrap)
2692		fLayoutData->preferred.width = min.width + 5 * lineHeight;
2693	else {
2694		float maxWidth = fLines->MaxWidth();
2695		if (maxWidth < min.width)
2696			maxWidth = min.width;
2697
2698		fLayoutData->preferred.width
2699			= maxWidth + fLayoutData->leftInset + fLayoutData->rightInset;
2700	}
2701
2702	fLayoutData->valid = true;
2703	ResetLayoutInvalidation();
2704
2705	TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
2706}
2707
2708
2709//	#pragma mark -
2710
2711
2712void
2713BTextView::AllAttached()
2714{
2715	BView::AllAttached();
2716}
2717
2718
2719void
2720BTextView::AllDetached()
2721{
2722	BView::AllDetached();
2723}
2724
2725
2726/* static */
2727text_run_array*
2728BTextView::AllocRunArray(int32 entryCount, int32* outSize)
2729{
2730	int32 size = sizeof(text_run_array) + (entryCount - 1) * sizeof(text_run);
2731
2732	text_run_array* runArray = (text_run_array*)calloc(size, 1);
2733	if (runArray == NULL) {
2734		if (outSize != NULL)
2735			*outSize = 0;
2736		return NULL;
2737	}
2738
2739	runArray->count = entryCount;
2740
2741	// Call constructors explicitly as the text_run_array
2742	// was allocated with malloc (and has to, for backwards
2743	// compatibility)
2744	for (int32 i = 0; i < runArray->count; i++) {
2745		new (&runArray->runs[i].font) BFont;
2746	}
2747
2748	if (outSize != NULL)
2749		*outSize = size;
2750
2751	return runArray;
2752}
2753
2754
2755/* static */
2756text_run_array*
2757BTextView::CopyRunArray(const text_run_array* orig, int32 countDelta)
2758{
2759	text_run_array* copy = AllocRunArray(countDelta, NULL);
2760	if (copy != NULL) {
2761		for (int32 i = 0; i < countDelta; i++) {
2762			copy->runs[i].offset = orig->runs[i].offset;
2763			copy->runs[i].font = orig->runs[i].font;
2764			copy->runs[i].color = orig->runs[i].color;
2765		}
2766	}
2767	return copy;
2768}
2769
2770
2771/* static */
2772void
2773BTextView::FreeRunArray(text_run_array* array)
2774{
2775	if (array == NULL)
2776		return;
2777
2778	// Call destructors explicitly
2779	for (int32 i = 0; i < array->count; i++)
2780		array->runs[i].font.~BFont();
2781
2782	free(array);
2783}
2784
2785
2786/* static */
2787void*
2788BTextView::FlattenRunArray(const text_run_array* runArray, int32* _size)
2789{
2790	CALLED();
2791	int32 size = sizeof(flattened_text_run_array) + (runArray->count - 1)
2792		* sizeof(flattened_text_run);
2793
2794	flattened_text_run_array* array = (flattened_text_run_array*)malloc(size);
2795	if (array == NULL) {
2796		if (_size)
2797			*_size = 0;
2798		return NULL;
2799	}
2800
2801	array->magic = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayMagic);
2802	array->version = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayVersion);
2803	array->count = B_HOST_TO_BENDIAN_INT32(runArray->count);
2804
2805	for (int32 i = 0; i < runArray->count; i++) {
2806		array->styles[i].offset = B_HOST_TO_BENDIAN_INT32(
2807			runArray->runs[i].offset);
2808		runArray->runs[i].font.GetFamilyAndStyle(&array->styles[i].family,
2809			&array->styles[i].style);
2810		array->styles[i].size = B_HOST_TO_BENDIAN_FLOAT(
2811			runArray->runs[i].font.Size());
2812		array->styles[i].shear = B_HOST_TO_BENDIAN_FLOAT(
2813			runArray->runs[i].font.Shear());
2814		array->styles[i].face = B_HOST_TO_BENDIAN_INT16(
2815			runArray->runs[i].font.Face());
2816		array->styles[i].red = runArray->runs[i].color.red;
2817		array->styles[i].green = runArray->runs[i].color.green;
2818		array->styles[i].blue = runArray->runs[i].color.blue;
2819		array->styles[i].alpha = 255;
2820		array->styles[i]._reserved_ = 0;
2821	}
2822
2823	if (_size)
2824		*_size = size;
2825
2826	return array;
2827}
2828
2829
2830/* static */
2831text_run_array*
2832BTextView::UnflattenRunArray(const void* data, int32* _size)
2833{
2834	CALLED();
2835	flattened_text_run_array* array = (flattened_text_run_array*)data;
2836
2837	if (B_BENDIAN_TO_HOST_INT32(array->magic) != kFlattenedTextRunArrayMagic
2838		|| B_BENDIAN_TO_HOST_INT32(array->version)
2839			!= kFlattenedTextRunArrayVersion) {
2840		if (_size)
2841			*_size = 0;
2842
2843		return NULL;
2844	}
2845
2846	int32 count = B_BENDIAN_TO_HOST_INT32(array->count);
2847
2848	text_run_array* runArray = AllocRunArray(count, _size);
2849	if (runArray == NULL)
2850		return NULL;
2851
2852	for (int32 i = 0; i < count; i++) {
2853		runArray->runs[i].offset = B_BENDIAN_TO_HOST_INT32(
2854			array->styles[i].offset);
2855
2856		// Set family and style independently from each other, so that
2857		// even if the family doesn't exist, we try to preserve the style
2858		runArray->runs[i].font.SetFamilyAndStyle(array->styles[i].family, NULL);
2859		runArray->runs[i].font.SetFamilyAndStyle(NULL, array->styles[i].style);
2860
2861		runArray->runs[i].font.SetSize(B_BENDIAN_TO_HOST_FLOAT(
2862			array->styles[i].size));
2863		runArray->runs[i].font.SetShear(B_BENDIAN_TO_HOST_FLOAT(
2864			array->styles[i].shear));
2865
2866		uint16 face = B_BENDIAN_TO_HOST_INT16(array->styles[i].face);
2867		if (face != B_REGULAR_FACE) {
2868			// Be's version doesn't seem to set this correctly
2869			runArray->runs[i].font.SetFace(face);
2870		}
2871
2872		runArray->runs[i].color.red = array->styles[i].red;
2873		runArray->runs[i].color.green = array->styles[i].green;
2874		runArray->runs[i].color.blue = array->styles[i].blue;
2875		runArray->runs[i].color.alpha = array->styles[i].alpha;
2876	}
2877
2878	return runArray;
2879}
2880
2881
2882void
2883BTextView::InsertText(const char* text, int32 length, int32 offset,
2884	const text_run_array* runs)
2885{
2886	CALLED();
2887
2888	if (length < 0)
2889		length = 0;
2890
2891	if (offset < 0)
2892		offset = 0;
2893	else if (offset > fText->Length())
2894		offset = fText->Length();
2895
2896	if (length > 0) {
2897		// add the text to the buffer
2898		fText->InsertText(text, length, offset);
2899
2900		// update the start offsets of each line below offset
2901		fLines->BumpOffset(length, _LineAt(offset) + 1);
2902
2903		// update the style runs
2904		fStyles->BumpOffset(length, fStyles->OffsetToRun(offset - 1) + 1);
2905
2906		// offset the caret/selection, if the text was inserted before it
2907		if (offset <= fSelEnd) {
2908			fSelStart += length;
2909			fCaretOffset = fSelEnd = fSelStart;
2910		}
2911	}
2912
2913	if (fStylable && runs != NULL) {
2914		_SetRunArray(offset, offset + length, runs);
2915	} else {
2916		// apply null-style to inserted text
2917		_ApplyStyleRange(offset, offset + length);
2918	}
2919}
2920
2921
2922void
2923BTextView::DeleteText(int32 fromOffset, int32 toOffset)
2924{
2925	CALLED();
2926
2927	if (fromOffset < 0)
2928		fromOffset = 0;
2929	else if (fromOffset > fText->Length())
2930		fromOffset = fText->Length();
2931
2932	if (toOffset < 0)
2933		toOffset = 0;
2934	else if (toOffset > fText->Length())
2935		toOffset = fText->Length();
2936
2937	if (fromOffset >= toOffset)
2938		return;
2939
2940	// set nullStyle to style at beginning of range
2941	fStyles->InvalidateNullStyle();
2942	fStyles->SyncNullStyle(fromOffset);
2943
2944	// remove from the text buffer
2945	fText->RemoveRange(fromOffset, toOffset);
2946
2947	// remove any lines that have been obliterated
2948	fLines->RemoveLineRange(fromOffset, toOffset);
2949
2950	// remove any style runs that have been obliterated
2951	fStyles->RemoveStyleRange(fromOffset, toOffset);
2952
2953	// adjust the selection accordingly, assumes fSelEnd >= fSelStart!
2954	int32 range = toOffset - fromOffset;
2955	if (fSelStart >= toOffset) {
2956		// selection is behind the range that was removed
2957		fSelStart -= range;
2958		fSelEnd -= range;
2959	} else if (fSelStart >= fromOffset && fSelEnd <= toOffset) {
2960		// the selection is within the range that was removed
2961		fSelStart = fSelEnd = fromOffset;
2962	} else if (fSelStart >= fromOffset && fSelEnd > toOffset) {
2963		// the selection starts within and ends after the range
2964		// the remaining part is the part that was after the range
2965		fSelStart = fromOffset;
2966		fSelEnd = fromOffset + fSelEnd - toOffset;
2967	} else if (fSelStart < fromOffset && fSelEnd < toOffset) {
2968		// the selection starts before, but ends within the range
2969		fSelEnd = fromOffset;
2970	} else if (fSelStart < fromOffset && fSelEnd >= toOffset) {
2971		// the selection starts before and ends after the range
2972		fSelEnd -= range;
2973	}
2974}
2975
2976
2977/*!	Undoes the last changes.
2978
2979	\param clipboard A \a clipboard to use for the undo operation.
2980*/
2981void
2982BTextView::Undo(BClipboard* clipboard)
2983{
2984	if (fUndo)
2985		fUndo->Undo(clipboard);
2986}
2987
2988
2989undo_state
2990BTextView::UndoState(bool* isRedo) const
2991{
2992	return fUndo == NULL ? B_UNDO_UNAVAILABLE : fUndo->State(isRedo);
2993}
2994
2995
2996//	#pragma mark - GetDragParameters() is protected
2997
2998
2999void
3000BTextView::GetDragParameters(BMessage* drag, BBitmap** bitmap, BPoint* point,
3001	BHandler** handler)
3002{
3003	CALLED();
3004	if (drag == NULL)
3005		return;
3006
3007	// Add originator and action
3008	drag->AddPointer("be:originator", this);
3009	drag->AddInt32("be_actions", B_TRASH_TARGET);
3010
3011	// add the text
3012	int32 numBytes = fSelEnd - fSelStart;
3013	const char* text = fText->GetString(fSelStart, &numBytes);
3014	drag->AddData("text/plain", B_MIME_TYPE, text, numBytes);
3015
3016	// add the corresponding styles
3017	int32 size = 0;
3018	text_run_array* styles = RunArray(fSelStart, fSelEnd, &size);
3019
3020	if (styles != NULL) {
3021		drag->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
3022			styles, size);
3023
3024		FreeRunArray(styles);
3025	}
3026
3027	if (bitmap != NULL)
3028		*bitmap = NULL;
3029
3030	if (handler != NULL)
3031		*handler = NULL;
3032}
3033
3034
3035//	#pragma mark - FBC padding and forbidden methods
3036
3037
3038void BTextView::_ReservedTextView3() {}
3039void BTextView::_ReservedTextView4() {}
3040void BTextView::_ReservedTextView5() {}
3041void BTextView::_ReservedTextView6() {}
3042void BTextView::_ReservedTextView7() {}
3043void BTextView::_ReservedTextView8() {}
3044void BTextView::_ReservedTextView9() {}
3045void BTextView::_ReservedTextView10() {}
3046void BTextView::_ReservedTextView11() {}
3047void BTextView::_ReservedTextView12() {}
3048
3049
3050// #pragma mark - Private methods
3051
3052
3053/*!	Inits the BTextView object.
3054
3055	\param textRect The BTextView's text rect.
3056	\param initialFont The font which the BTextView will use.
3057	\param initialColor The initial color of the text.
3058*/
3059void
3060BTextView::_InitObject(BRect textRect, const BFont* initialFont,
3061	const rgb_color* initialColor)
3062{
3063	BFont font;
3064	if (initialFont == NULL)
3065		GetFont(&font);
3066	else
3067		font = *initialFont;
3068
3069	_NormalizeFont(&font);
3070
3071	rgb_color documentTextColor = ui_color(B_DOCUMENT_TEXT_COLOR);
3072
3073	if (initialColor == NULL)
3074		initialColor = &documentTextColor;
3075
3076	fText = new BPrivate::TextGapBuffer;
3077	fLines = new LineBuffer;
3078	fStyles = new StyleBuffer(&font, initialColor);
3079
3080	fInstalledNavigateCommandWordwiseShortcuts = false;
3081	fInstalledNavigateOptionWordwiseShortcuts = false;
3082	fInstalledNavigateOptionLinewiseShortcuts = false;
3083	fInstalledNavigateHomeEndDocwiseShortcuts = false;
3084
3085	fInstalledSelectCommandWordwiseShortcuts = false;
3086	fInstalledSelectOptionWordwiseShortcuts = false;
3087	fInstalledSelectOptionLinewiseShortcuts = false;
3088	fInstalledSelectHomeEndDocwiseShortcuts = false;
3089
3090	fInstalledRemoveCommandWordwiseShortcuts = false;
3091	fInstalledRemoveOptionWordwiseShortcuts = false;
3092
3093	// We put these here instead of in the constructor initializer list
3094	// to have less code duplication, and a single place where to do changes
3095	// if needed.
3096	fTextRect = textRect;
3097		// NOTE: The only places where text rect is changed:
3098		// * width is possibly adjusted in _AutoResize(),
3099		// * height is adjusted in _RecalculateLineBreaks().
3100		// When used within the layout management framework, the
3101		// text rect is changed to maintain constant insets.
3102	fMinTextRectWidth = fTextRect.Width();
3103		// see SetTextRect()
3104	fSelStart = fSelEnd = 0;
3105	fCaretVisible = false;
3106	fCaretTime = 0;
3107	fCaretOffset = 0;
3108	fClickCount = 0;
3109	fClickTime = 0;
3110	fDragOffset = -1;
3111	fCursor = 0;
3112	fActive = false;
3113	fStylable = false;
3114	fTabWidth = 28.0;
3115	fSelectable = true;
3116	fEditable = true;
3117	fWrap = true;
3118	fMaxBytes = INT32_MAX;
3119	fDisallowedChars = NULL;
3120	fAlignment = B_ALIGN_LEFT;
3121	fAutoindent = false;
3122	fOffscreen = NULL;
3123	fColorSpace = B_CMAP8;
3124	fResizable = false;
3125	fContainerView = NULL;
3126	fUndo = NULL;
3127	fInline = NULL;
3128	fDragRunner = NULL;
3129	fClickRunner = NULL;
3130	fTrackingMouse = NULL;
3131
3132	fLayoutData = new LayoutData;
3133	fLayoutData->UpdateInsets(Bounds().OffsetToCopy(B_ORIGIN), fTextRect);
3134
3135	fLastClickOffset = -1;
3136
3137	SetDoesUndo(true);
3138}
3139
3140
3141//!	Handles when Backspace key is pressed.
3142void
3143BTextView::_HandleBackspace(int32 modifiers)
3144{
3145	if (modifiers < 0) {
3146		BMessage* currentMessage = Window()->CurrentMessage();
3147		if (currentMessage == NULL
3148			|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
3149			modifiers = 0;
3150		}
3151	}
3152
3153	bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
3154	bool optionKeyDown  = (modifiers & B_OPTION_KEY)  != 0;
3155	bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
3156
3157	if ((commandKeyDown || optionKeyDown) && !controlKeyDown) {
3158		fSelStart = _PreviousWordStart(fCaretOffset - 1);
3159		fSelEnd = fCaretOffset;
3160	}
3161
3162	if (fUndo) {
3163		TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(
3164			fUndo);
3165		if (!undoBuffer) {
3166			delete fUndo;
3167			fUndo = undoBuffer = new TypingUndoBuffer(this);
3168		}
3169		undoBuffer->BackwardErase();
3170	}
3171
3172	if (fSelStart == fSelEnd) {
3173		if (fSelStart == 0)
3174			return;
3175		else
3176			fSelStart = _PreviousInitialByte(fSelStart);
3177	} else
3178		Highlight(fSelStart, fSelEnd);
3179
3180	DeleteText(fSelStart, fSelEnd);
3181	fCaretOffset = fSelEnd = fSelStart;
3182
3183	_Refresh(fSelStart, fSelEnd, true);
3184}
3185
3186
3187//!	Handles when an arrow key is pressed.
3188void
3189BTextView::_HandleArrowKey(uint32 arrowKey, int32 modifiers)
3190{
3191	// return if there's nowhere to go
3192	if (fText->Length() == 0)
3193		return;
3194
3195	int32 selStart = fSelStart;
3196	int32 selEnd = fSelEnd;
3197
3198	if (modifiers < 0) {
3199		BMessage* currentMessage = Window()->CurrentMessage();
3200		if (currentMessage == NULL
3201			|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
3202			modifiers = 0;
3203		}
3204	}
3205
3206	bool shiftKeyDown   = (modifiers & B_SHIFT_KEY)   != 0;
3207	bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
3208	bool optionKeyDown  = (modifiers & B_OPTION_KEY)  != 0;
3209	bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
3210
3211	int32 lastClickOffset = fCaretOffset;
3212
3213	switch (arrowKey) {
3214		case B_LEFT_ARROW:
3215			if (!fEditable)
3216				_ScrollBy(-1 * kHorizontalScrollBarStep, 0);
3217			else if (fSelStart != fSelEnd && !shiftKeyDown)
3218				fCaretOffset = fSelStart;
3219			else {
3220				if ((commandKeyDown || optionKeyDown) && !controlKeyDown)
3221					fCaretOffset = _PreviousWordStart(fCaretOffset - 1);
3222				else
3223					fCaretOffset = _PreviousInitialByte(fCaretOffset);
3224
3225				if (shiftKeyDown && fCaretOffset != lastClickOffset) {
3226					if (fCaretOffset < fSelStart) {
3227						// extend selection to the left
3228						selStart = fCaretOffset;
3229						if (lastClickOffset > fSelStart) {
3230							// caret has jumped across "anchor"
3231							selEnd = fSelStart;
3232						}
3233					} else {
3234						// shrink selection from the right
3235						selEnd = fCaretOffset;
3236					}
3237				}
3238			}
3239			break;
3240
3241		case B_RIGHT_ARROW:
3242			if (!fEditable)
3243				_ScrollBy(kHorizontalScrollBarStep, 0);
3244			else if (fSelStart != fSelEnd && !shiftKeyDown)
3245				fCaretOffset = fSelEnd;
3246			else {
3247				if ((commandKeyDown || optionKeyDown) && !controlKeyDown)
3248					fCaretOffset = _NextWordEnd(fCaretOffset);
3249				else
3250					fCaretOffset = _NextInitialByte(fCaretOffset);
3251
3252				if (shiftKeyDown && fCaretOffset != lastClickOffset) {
3253					if (fCaretOffset > fSelEnd) {
3254						// extend selection to the right
3255						selEnd = fCaretOffset;
3256						if (lastClickOffset < fSelEnd) {
3257							// caret has jumped across "anchor"
3258							selStart = fSelEnd;
3259						}
3260					} else {
3261						// shrink selection from the left
3262						selStart = fCaretOffset;
3263					}
3264				}
3265			}
3266			break;
3267
3268		case B_UP_ARROW:
3269		{
3270			if (!fEditable)
3271				_ScrollBy(0, -1 * kVerticalScrollBarStep);
3272			else if (fSelStart != fSelEnd && !shiftKeyDown)
3273				fCaretOffset = fSelStart;
3274			else {
3275				if (optionKeyDown && !commandKeyDown && !controlKeyDown)
3276					fCaretOffset = _PreviousLineStart(fCaretOffset);
3277				else if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
3278					_ScrollTo(0, 0);
3279					fCaretOffset = 0;
3280				} else {
3281					float height;
3282					BPoint point = PointAt(fCaretOffset, &height);
3283					// find the caret position on the previous
3284					// line by gently stepping onto this line
3285					for (int i = 1; i <= height; i++) {
3286						point.y--;
3287						int32 offset = OffsetAt(point);
3288						if (offset < fCaretOffset || i == height) {
3289							fCaretOffset = offset;
3290							break;
3291						}
3292					}
3293				}
3294
3295				if (shiftKeyDown && fCaretOffset != lastClickOffset) {
3296					if (fCaretOffset < fSelStart) {
3297						// extend selection to the top
3298						selStart = fCaretOffset;
3299						if (lastClickOffset > fSelStart) {
3300							// caret has jumped across "anchor"
3301							selEnd = fSelStart;
3302						}
3303					} else {
3304						// shrink selection from the bottom
3305						selEnd = fCaretOffset;
3306					}
3307				}
3308			}
3309			break;
3310		}
3311
3312		case B_DOWN_ARROW:
3313		{
3314			if (!fEditable)
3315				_ScrollBy(0, kVerticalScrollBarStep);
3316			else if (fSelStart != fSelEnd && !shiftKeyDown)
3317				fCaretOffset = fSelEnd;
3318			else {
3319				if (optionKeyDown && !commandKeyDown && !controlKeyDown)
3320					fCaretOffset = _NextLineEnd(fCaretOffset);
3321				else if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
3322					_ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset);
3323					fCaretOffset = fText->Length();
3324				} else {
3325					float height;
3326					BPoint point = PointAt(fCaretOffset, &height);
3327					point.y += height;
3328					fCaretOffset = OffsetAt(point);
3329				}
3330
3331				if (shiftKeyDown && fCaretOffset != lastClickOffset) {
3332					if (fCaretOffset > fSelEnd) {
3333						// extend selection to the bottom
3334						selEnd = fCaretOffset;
3335						if (lastClickOffset < fSelEnd) {
3336							// caret has jumped across "anchor"
3337							selStart = fSelEnd;
3338						}
3339					} else {
3340						// shrink selection from the top
3341						selStart = fCaretOffset;
3342					}
3343				}
3344			}
3345			break;
3346		}
3347	}
3348
3349	fStyles->InvalidateNullStyle();
3350
3351	if (fEditable) {
3352		if (shiftKeyDown)
3353			Select(selStart, selEnd);
3354		else
3355			Select(fCaretOffset, fCaretOffset);
3356
3357		// scroll if needed
3358		ScrollToOffset(fCaretOffset);
3359	}
3360}
3361
3362
3363//!	Handles when the Delete key is pressed.
3364void
3365BTextView::_HandleDelete(int32 modifiers)
3366{
3367	if (modifiers < 0) {
3368		BMessage* currentMessage = Window()->CurrentMessage();
3369		if (currentMessage == NULL
3370			|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
3371			modifiers = 0;
3372		}
3373	}
3374
3375	bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
3376	bool optionKeyDown  = (modifiers & B_OPTION_KEY)  != 0;
3377	bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
3378
3379	if ((commandKeyDown || optionKeyDown) && !controlKeyDown) {
3380		fSelStart = fCaretOffset;
3381		fSelEnd = _NextWordEnd(fCaretOffset) + 1;
3382	}
3383
3384	if (fUndo) {
3385		TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(
3386			fUndo);
3387		if (!undoBuffer) {
3388			delete fUndo;
3389			fUndo = undoBuffer = new TypingUndoBuffer(this);
3390		}
3391		undoBuffer->ForwardErase();
3392	}
3393
3394	if (fSelStart == fSelEnd) {
3395		if (fSelEnd == fText->Length())
3396			return;
3397		else
3398			fSelEnd = _NextInitialByte(fSelEnd);
3399	} else
3400		Highlight(fSelStart, fSelEnd);
3401
3402	DeleteText(fSelStart, fSelEnd);
3403	fCaretOffset = fSelEnd = fSelStart;
3404
3405	_Refresh(fSelStart, fSelEnd, true);
3406}
3407
3408
3409//!	Handles when the Page Up, Page Down, Home, or End key is pressed.
3410void
3411BTextView::_HandlePageKey(uint32 pageKey, int32 modifiers)
3412{
3413	if (modifiers < 0) {
3414		BMessage* currentMessage = Window()->CurrentMessage();
3415		if (currentMessage == NULL
3416			|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
3417			modifiers = 0;
3418		}
3419	}
3420
3421	bool shiftKeyDown   = (modifiers & B_SHIFT_KEY)   != 0;
3422	bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
3423	bool optionKeyDown  = (modifiers & B_OPTION_KEY)  != 0;
3424	bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
3425
3426	STELine* line = NULL;
3427	int32 selStart = fSelStart;
3428	int32 selEnd = fSelEnd;
3429
3430	int32 lastClickOffset = fCaretOffset;
3431	switch (pageKey) {
3432		case B_HOME:
3433			if (!fEditable) {
3434				fCaretOffset = 0;
3435				_ScrollTo(0, 0);
3436				break;
3437			} else {
3438				if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
3439					_ScrollTo(0, 0);
3440					fCaretOffset = 0;
3441				} else {
3442					// get the start of the last line if caret is on it
3443					line = (*fLines)[_LineAt(lastClickOffset)];
3444					fCaretOffset = line->offset;
3445				}
3446
3447				if (!shiftKeyDown)
3448					selStart = selEnd = fCaretOffset;
3449				else if (fCaretOffset != lastClickOffset) {
3450					if (fCaretOffset < fSelStart) {
3451						// extend selection to the left
3452						selStart = fCaretOffset;
3453						if (lastClickOffset > fSelStart) {
3454							// caret has jumped across "anchor"
3455							selEnd = fSelStart;
3456						}
3457					} else {
3458						// shrink selection from the right
3459						selEnd = fCaretOffset;
3460					}
3461				}
3462			}
3463			break;
3464
3465		case B_END:
3466			if (!fEditable) {
3467				fCaretOffset = fText->Length();
3468				_ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset);
3469				break;
3470			} else {
3471				if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
3472					_ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset);
3473					fCaretOffset = fText->Length();
3474				} else {
3475					// If we are on the last line, just go to the last
3476					// character in the buffer, otherwise get the starting
3477					// offset of the next line, and go to the previous character
3478					int32 currentLine = _LineAt(lastClickOffset);
3479					if (currentLine + 1 < fLines->NumLines()) {
3480						line = (*fLines)[currentLine + 1];
3481						fCaretOffset = _PreviousInitialByte(line->offset);
3482					} else {
3483						// This check is needed to avoid moving the cursor
3484						// when the cursor is on the last line, and that line
3485						// is empty
3486						if (fCaretOffset != fText->Length()) {
3487							fCaretOffset = fText->Length();
3488							if (ByteAt(fCaretOffset - 1) == B_ENTER)
3489								fCaretOffset--;
3490						}
3491					}
3492				}
3493
3494				if (!shiftKeyDown)
3495					selStart = selEnd = fCaretOffset;
3496				else if (fCaretOffset != lastClickOffset) {
3497					if (fCaretOffset > fSelEnd) {
3498						// extend selection to the right
3499						selEnd = fCaretOffset;
3500						if (lastClickOffset < fSelEnd) {
3501							// caret has jumped across "anchor"
3502							selStart = fSelEnd;
3503						}
3504					} else {
3505						// shrink selection from the left
3506						selStart = fCaretOffset;
3507					}
3508				}
3509			}
3510			break;
3511
3512		case B_PAGE_UP:
3513		{
3514			float lineHeight;
3515			BPoint currentPos = PointAt(fCaretOffset, &lineHeight);
3516			BPoint nextPos(currentPos.x,
3517				currentPos.y + lineHeight - Bounds().Height());
3518			fCaretOffset = OffsetAt(nextPos);
3519			nextPos = PointAt(fCaretOffset);
3520			_ScrollBy(0, nextPos.y - currentPos.y);
3521
3522			if (!fEditable)
3523				break;
3524
3525			if (!shiftKeyDown)
3526				selStart = selEnd = fCaretOffset;
3527			else if (fCaretOffset != lastClickOffset) {
3528				if (fCaretOffset < fSelStart) {
3529					// extend selection to the top
3530					selStart = fCaretOffset;
3531					if (lastClickOffset > fSelStart) {
3532						// caret has jumped across "anchor"
3533						selEnd = fSelStart;
3534					}
3535				} else {
3536					// shrink selection from the bottom
3537					selEnd = fCaretOffset;
3538				}
3539			}
3540
3541			break;
3542		}
3543
3544		case B_PAGE_DOWN:
3545		{
3546			BPoint currentPos = PointAt(fCaretOffset);
3547			BPoint nextPos(currentPos.x, currentPos.y + Bounds().Height());
3548			fCaretOffset = OffsetAt(nextPos);
3549			nextPos = PointAt(fCaretOffset);
3550			_ScrollBy(0, nextPos.y - currentPos.y);
3551
3552			if (!fEditable)
3553				break;
3554
3555			if (!shiftKeyDown)
3556				selStart = selEnd = fCaretOffset;
3557			else if (fCaretOffset != lastClickOffset) {
3558				if (fCaretOffset > fSelEnd) {
3559					// extend selection to the bottom
3560					selEnd = fCaretOffset;
3561					if (lastClickOffset < fSelEnd) {
3562						// caret has jumped across "anchor"
3563						selStart = fSelEnd;
3564					}
3565				} else {
3566					// shrink selection from the top
3567					selStart = fCaretOffset;
3568				}
3569			}
3570
3571			break;
3572		}
3573	}
3574
3575	if (fEditable) {
3576		if (shiftKeyDown)
3577			Select(selStart, selEnd);
3578		else
3579			Select(fCaretOffset, fCaretOffset);
3580
3581		ScrollToOffset(fCaretOffset);
3582	}
3583}
3584
3585
3586/*!	Handles when an alpha-numeric key is pressed.
3587
3588	\param bytes The string or character associated with the key.
3589	\param numBytes The amount of bytes containes in "bytes".
3590*/
3591void
3592BTextView::_HandleAlphaKey(const char* bytes, int32 numBytes)
3593{
3594	// TODO: block input if not editable (Andrew)
3595	if (fUndo) {
3596		TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo);
3597		if (!undoBuffer) {
3598			delete fUndo;
3599			fUndo = undoBuffer = new TypingUndoBuffer(this);
3600		}
3601		undoBuffer->InputCharacter(numBytes);
3602	}
3603
3604	if (fSelStart != fSelEnd) {
3605		Highlight(fSelStart, fSelEnd);
3606		DeleteText(fSelStart, fSelEnd);
3607	}
3608
3609	if (fAutoindent && numBytes == 1 && *bytes == B_ENTER) {
3610		int32 start, offset;
3611		start = offset = OffsetAt(_LineAt(fSelStart));
3612
3613		while (ByteAt(offset) != '\0' &&
3614				(ByteAt(offset) == B_TAB || ByteAt(offset) == B_SPACE)
3615				&& offset < fSelStart)
3616			offset++;
3617
3618		_DoInsertText(bytes, numBytes, fSelStart, NULL);
3619		if (start != offset)
3620			_DoInsertText(Text() + start, offset - start, fSelStart, NULL);
3621	} else
3622		_DoInsertText(bytes, numBytes, fSelStart, NULL);
3623
3624	fCaretOffset = fSelEnd;
3625
3626	ScrollToOffset(fCaretOffset);
3627}
3628
3629
3630/*!	Redraw the text between the two given offsets, recalculating line-breaks
3631	if needed.
3632
3633	\param fromOffset The offset from where to refresh.
3634	\param toOffset The offset where to refresh to.
3635	\param scroll If \c true, scroll the view to the end offset.
3636*/
3637void
3638BTextView::_Refresh(int32 fromOffset, int32 toOffset, bool scroll)
3639{
3640	// TODO: Cleanup
3641	float saveHeight = fTextRect.Height();
3642	float saveWidth = fTextRect.Width();
3643	int32 fromLine = _LineAt(fromOffset);
3644	int32 toLine = _LineAt(toOffset);
3645	int32 saveFromLine = fromLine;
3646	int32 saveToLine = toLine;
3647
3648	_RecalculateLineBreaks(&fromLine, &toLine);
3649
3650	// TODO: Maybe there is still something we can do without a window...
3651	if (!Window())
3652		return;
3653
3654	BRect bounds = Bounds();
3655	float newHeight = fTextRect.Height();
3656
3657	// if the line breaks have changed, force an erase
3658	if (fromLine != saveFromLine || toLine != saveToLine
3659			|| newHeight != saveHeight) {
3660		fromOffset = -1;
3661	}
3662
3663	if (newHeight != saveHeight) {
3664		// the text area has changed
3665		if (newHeight < saveHeight)
3666			toLine = _LineAt(BPoint(0.0f, saveHeight + fTextRect.top));
3667		else
3668			toLine = _LineAt(BPoint(0.0f, newHeight + fTextRect.top));
3669	}
3670
3671	// draw only those lines that are visible
3672	int32 fromVisible = _LineAt(BPoint(0.0f, bounds.top));
3673	int32 toVisible = _LineAt(BPoint(0.0f, bounds.bottom));
3674	fromLine = max_c(fromVisible, fromLine);
3675	toLine = min_c(toLine, toVisible);
3676
3677	_AutoResize(false);
3678
3679	_RequestDrawLines(fromLine, toLine);
3680
3681	// erase the area below the text
3682	BRect eraseRect = bounds;
3683	eraseRect.top = fTextRect.top + (*fLines)[fLines->NumLines()]->origin;
3684	eraseRect.bottom = fTextRect.top + saveHeight;
3685	if (eraseRect.bottom > eraseRect.top && eraseRect.Intersects(bounds)) {
3686		SetLowColor(ViewColor());
3687		FillRect(eraseRect, B_SOLID_LOW);
3688	}
3689
3690	// update the scroll bars if the text area has changed
3691	if (newHeight != saveHeight || fMinTextRectWidth != saveWidth)
3692		_UpdateScrollbars();
3693
3694	if (scroll)
3695		ScrollToOffset(fSelEnd);
3696
3697	Flush();
3698}
3699
3700
3701/*!	Recalculate line breaks between two lines.
3702
3703	\param startLine The line number to start recalculating line breaks.
3704	\param endLine The line number to stop recalculating line breaks.
3705*/
3706void
3707BTextView::_RecalculateLineBreaks(int32* startLine, int32* endLine)
3708{
3709	CALLED();
3710
3711	// are we insane?
3712	*startLine = (*startLine < 0) ? 0 : *startLine;
3713	*endLine = (*endLine > fLines->NumLines() - 1) ? fLines->NumLines() - 1
3714		: *endLine;
3715
3716	int32 textLength = fText->Length();
3717	int32 lineIndex = (*startLine > 0) ? *startLine - 1 : 0;
3718	int32 recalThreshold = (*fLines)[*endLine + 1]->offset;
3719	float width = max_c(fTextRect.Width(), 10);
3720		// TODO: The minimum width of 10 is a work around for the following
3721		// problem: If the text rect is too small, we are not calculating any
3722		// line heights, not even for the first line. Maybe this is a bug
3723		// in the algorithm, but other places in the class rely on at least
3724		// the first line to return a valid height. Maybe "10" should really
3725		// be the width of the very first glyph instead.
3726	STELine* curLine = (*fLines)[lineIndex];
3727	STELine* nextLine = curLine + 1;
3728
3729	do {
3730		float ascent, descent;
3731		int32 fromOffset = curLine->offset;
3732		int32 toOffset = _FindLineBreak(fromOffset, &ascent, &descent, &width);
3733
3734		curLine->ascent = ascent;
3735		curLine->width = width;
3736
3737		// we want to advance at least by one character
3738		int32 nextOffset = _NextInitialByte(fromOffset);
3739		if (toOffset < nextOffset && fromOffset < textLength)
3740			toOffset = nextOffset;
3741
3742		lineIndex++;
3743		STELine saveLine = *nextLine;
3744		if (lineIndex > fLines->NumLines() || toOffset < nextLine->offset) {
3745			// the new line comes before the old line start, add a line
3746			STELine newLine;
3747			newLine.offset = toOffset;
3748			newLine.origin = ceilf(curLine->origin + ascent + descent) + 1;
3749			newLine.ascent = 0;
3750			fLines->InsertLine(&newLine, lineIndex);
3751		} else {
3752			// update the existing line
3753			nextLine->offset = toOffset;
3754			nextLine->origin = ceilf(curLine->origin + ascent + descent) + 1;
3755
3756			// remove any lines that start before the current line
3757			while (lineIndex < fLines->NumLines()
3758				&& toOffset >= ((*fLines)[lineIndex] + 1)->offset) {
3759				fLines->RemoveLines(lineIndex + 1);
3760			}
3761
3762			nextLine = (*fLines)[lineIndex];
3763			if (nextLine->offset == saveLine.offset) {
3764				if (nextLine->offset >= recalThreshold) {
3765					if (nextLine->origin != saveLine.origin)
3766						fLines->BumpOrigin(nextLine->origin - saveLine.origin,
3767							lineIndex + 1);
3768					break;
3769				}
3770			} else {
3771				if (lineIndex > 0 && lineIndex == *startLine)
3772					*startLine = lineIndex - 1;
3773			}
3774		}
3775
3776		curLine = (*fLines)[lineIndex];
3777		nextLine = curLine + 1;
3778	} while (curLine->offset < textLength);
3779
3780	// make sure that the sentinel line (which starts at the end of the buffer)
3781	// has always a width of 0
3782	(*fLines)[fLines->NumLines()]->width = 0;
3783
3784	// update the text rect
3785	float newHeight = TextHeight(0, fLines->NumLines() - 1);
3786	fTextRect.bottom = fTextRect.top + newHeight;
3787	if (!fWrap) {
3788		fMinTextRectWidth = fLines->MaxWidth();
3789		fTextRect.right = ceilf(fTextRect.left + fMinTextRectWidth);
3790	}
3791
3792	*endLine = lineIndex - 1;
3793	*startLine = min_c(*startLine, *endLine);
3794}
3795
3796
3797int32
3798BTextView::_FindLineBreak(int32 fromOffset, float* _ascent, float* _descent,
3799	float* inOutWidth)
3800{
3801	*_ascent = 0.0;
3802	*_descent = 0.0;
3803
3804	const int32 limit = fText->Length();
3805
3806	// is fromOffset at the end?
3807	if (fromOffset >= limit) {
3808		// try to return valid height info anyway
3809		if (fStyles->NumRuns() > 0) {
3810			fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, _ascent,
3811				_descent);
3812		} else {
3813			if (fStyles->IsValidNullStyle()) {
3814				const BFont* font = NULL;
3815				fStyles->GetNullStyle(&font, NULL);
3816
3817				font_height fh;
3818				font->GetHeight(&fh);
3819				*_ascent = fh.ascent;
3820				*_descent = fh.descent + fh.leading;
3821			}
3822		}
3823		*inOutWidth = 0;
3824
3825		return limit;
3826	}
3827
3828	int32 offset = fromOffset;
3829
3830	if (!fWrap) {
3831		// Text wrapping is turned off.
3832		// Just find the offset of the first \n character
3833		offset = limit - fromOffset;
3834		fText->FindChar(B_ENTER, fromOffset, &offset);
3835		offset += fromOffset;
3836		int32 toOffset = (offset < limit) ? offset : limit;
3837
3838		*inOutWidth = _TabExpandedStyledWidth(fromOffset, toOffset - fromOffset,
3839			_ascent, _descent);
3840
3841		return offset < limit ? offset + 1 : limit;
3842	}
3843
3844	bool done = false;
3845	float ascent = 0.0;
3846	float descent = 0.0;
3847	int32 delta = 0;
3848	float deltaWidth = 0.0;
3849	float strWidth = 0.0;
3850	uchar theChar;
3851
3852	// wrap the text
3853	while (offset < limit && !done) {
3854		// find the next line break candidate
3855		for (; (offset + delta) < limit; delta++) {
3856			if (CanEndLine(offset + delta)) {
3857				theChar = fText->RealCharAt(offset + delta);
3858				if (theChar != B_SPACE && theChar != B_TAB
3859					&& theChar != B_ENTER) {
3860					// we are scanning for trailing whitespace below, so we
3861					// have to skip non-whitespace characters, that can end
3862					// the line, here
3863					delta++;
3864				}
3865				break;
3866			}
3867		}
3868
3869		int32 deltaBeforeWhitespace = delta;
3870		// now skip over trailing whitespace, if any
3871		for (; (offset + delta) < limit; delta++) {
3872			theChar = fText->RealCharAt(offset + delta);
3873			if (theChar == B_ENTER) {
3874				// found a newline, we're done!
3875				done = true;
3876				delta++;
3877				break;
3878			} else if (theChar != B_SPACE && theChar != B_TAB) {
3879				// stop at anything else than trailing whitespace
3880				break;
3881			}
3882		}
3883
3884		delta = max_c(delta, 1);
3885
3886		// do not include B_ENTER-terminator into width & height calculations
3887		deltaWidth = _TabExpandedStyledWidth(offset,
3888								done ? delta - 1 : delta, &ascent, &descent);
3889		strWidth += deltaWidth;
3890
3891		if (strWidth >= *inOutWidth) {
3892			// we've found where the line will wrap
3893			done = true;
3894
3895			// we have included trailing whitespace in the width computation
3896			// above, but that is not being shown anyway, so we try again
3897			// without the trailing whitespace
3898			if (delta == deltaBeforeWhitespace) {
3899				// there is no trailing whitespace, no point in trying
3900				break;
3901			}
3902
3903			// reset string width to start of current run ...
3904			strWidth -= deltaWidth;
3905
3906			// ... and compute the resulting width (of visible characters)
3907			strWidth += _StyledWidth(offset, deltaBeforeWhitespace, NULL, NULL);
3908			if (strWidth >= *inOutWidth) {
3909				// width of visible characters exceeds line, we need to wrap
3910				// before the current "word"
3911				break;
3912			}
3913		}
3914
3915		*_ascent = max_c(ascent, *_ascent);
3916		*_descent = max_c(descent, *_descent);
3917
3918		offset += delta;
3919		delta = 0;
3920	}
3921
3922	if (offset - fromOffset < 1) {
3923		// there weren't any words that fit entirely in this line
3924		// force a break in the middle of a word
3925		*_ascent = 0.0;
3926		*_descent = 0.0;
3927		strWidth = 0.0;
3928
3929		int32 current = fromOffset;
3930		for (offset = _NextInitialByte(current); current < limit;
3931				current = offset, offset = _NextInitialByte(offset)) {
3932			strWidth += _StyledWidth(current, offset - current, &ascent,
3933				&descent);
3934			if (strWidth >= *inOutWidth) {
3935				offset = _PreviousInitialByte(offset);
3936				break;
3937			}
3938
3939			*_ascent = max_c(ascent, *_ascent);
3940			*_descent = max_c(descent, *_descent);
3941		}
3942	}
3943
3944	return min_c(offset, limit);
3945}
3946
3947
3948int32
3949BTextView::_PreviousLineStart(int32 offset)
3950{
3951	if (offset <= 0)
3952		return 0;
3953
3954	while (offset > 0) {
3955		offset = _PreviousInitialByte(offset);
3956		if (_CharClassification(offset) == CHAR_CLASS_WHITESPACE
3957			&& ByteAt(offset) == B_ENTER) {
3958			return offset + 1;
3959		}
3960	}
3961
3962	return offset;
3963}
3964
3965
3966int32
3967BTextView::_NextLineEnd(int32 offset)
3968{
3969	int32 textLen = fText->Length();
3970	if (offset >= textLen)
3971		return textLen;
3972
3973	while (offset < textLen) {
3974		if (_CharClassification(offset) == CHAR_CLASS_WHITESPACE
3975			&& ByteAt(offset) == B_ENTER) {
3976			break;
3977		}
3978		offset = _NextInitialByte(offset);
3979	}
3980
3981	return offset;
3982}
3983
3984
3985int32
3986BTextView::_PreviousWordBoundary(int32 offset)
3987{
3988	uint32 charType = _CharClassification(offset);
3989	int32 previous;
3990	while (offset > 0) {
3991		previous = _PreviousInitialByte(offset);
3992		if (_CharClassification(previous) != charType)
3993			break;
3994		offset = previous;
3995	}
3996
3997	return offset;
3998}
3999
4000
4001int32
4002BTextView::_NextWordBoundary(int32 offset)
4003{
4004	int32 textLen = fText->Length();
4005	uint32 charType = _CharClassification(offset);
4006	while (offset < textLen) {
4007		offset = _NextInitialByte(offset);
4008		if (_CharClassification(offset) != charType)
4009			break;
4010	}
4011
4012	return offset;
4013}
4014
4015
4016int32
4017BTextView::_PreviousWordStart(int32 offset)
4018{
4019	if (offset <= 1)
4020		return 0;
4021
4022	--offset;
4023		// need to look at previous char
4024	if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) {
4025		// skip non-word characters
4026		while (offset > 0) {
4027			offset = _PreviousInitialByte(offset);
4028			if (_CharClassification(offset) == CHAR_CLASS_DEFAULT)
4029				break;
4030		}
4031	}
4032	while (offset > 0) {
4033		// skip to start of word
4034		int32 previous = _PreviousInitialByte(offset);
4035		if (_CharClassification(previous) != CHAR_CLASS_DEFAULT)
4036			break;
4037		offset = previous;
4038	}
4039
4040	return offset;
4041}
4042
4043
4044int32
4045BTextView::_NextWordEnd(int32 offset)
4046{
4047	int32 textLen = fText->Length();
4048	if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) {
4049		// skip non-word characters
4050		while (offset < textLen) {
4051			offset = _NextInitialByte(offset);
4052			if (_CharClassification(offset) == CHAR_CLASS_DEFAULT)
4053				break;
4054		}
4055	}
4056	while (offset < textLen) {
4057		// skip to end of word
4058		offset = _NextInitialByte(offset);
4059		if (_CharClassification(offset) != CHAR_CLASS_DEFAULT)
4060			break;
4061	}
4062
4063	return offset;
4064}
4065
4066
4067/*!	Returns the width used by the characters starting at the given
4068	offset with the given length, expanding all tab characters as needed.
4069*/
4070float
4071BTextView::_TabExpandedStyledWidth(int32 offset, int32 length, float* _ascent,
4072	float* _descent) const
4073{
4074	float ascent = 0.0;
4075	float descent = 0.0;
4076	float maxAscent = 0.0;
4077	float maxDescent = 0.0;
4078
4079	float width = 0.0;
4080	int32 numBytes = length;
4081	bool foundTab = false;
4082	do {
4083		foundTab = fText->FindChar(B_TAB, offset, &numBytes);
4084		width += _StyledWidth(offset, numBytes, &ascent, &descent);
4085
4086		if (maxAscent < ascent)
4087			maxAscent = ascent;
4088		if (maxDescent < descent)
4089			maxDescent = descent;
4090
4091		if (foundTab) {
4092			width += _ActualTabWidth(width);
4093			numBytes++;
4094		}
4095
4096		offset += numBytes;
4097		length -= numBytes;
4098		numBytes = length;
4099	} while (foundTab && length > 0);
4100
4101	if (_ascent != NULL)
4102		*_ascent = maxAscent;
4103	if (_descent != NULL)
4104		*_descent = maxDescent;
4105
4106	return width;
4107}
4108
4109
4110/*!	Calculate the width of the text within the given limits.
4111
4112	\param fromOffset The offset where to start.
4113	\param length The length of the text to examine.
4114	\param _ascent A pointer to a float which will contain the maximum ascent.
4115	\param _descent A pointer to a float which will contain the maximum descent.
4116
4117	\return The width for the text within the given limits.
4118*/
4119float
4120BTextView::_StyledWidth(int32 fromOffset, int32 length, float* _ascent,
4121	float* _descent) const
4122{
4123	if (length == 0) {
4124		// determine height of char at given offset, but return empty width
4125		fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, _ascent,
4126			_descent);
4127		return 0.0;
4128	}
4129
4130	float result = 0.0;
4131	float ascent = 0.0;
4132	float descent = 0.0;
4133	float maxAscent = 0.0;
4134	float maxDescent = 0.0;
4135
4136	// iterate through the style runs
4137	const BFont* font = NULL;
4138	int32 numBytes;
4139	while ((numBytes = fStyles->Iterate(fromOffset, length, fInline, &font,
4140			NULL, &ascent, &descent)) != 0) {
4141		maxAscent = max_c(ascent, maxAscent);
4142		maxDescent = max_c(descent, maxDescent);
4143
4144#if USE_WIDTHBUFFER
4145		// Use _BWidthBuffer_ if possible
4146		if (BPrivate::gWidthBuffer != NULL) {
4147			result += BPrivate::gWidthBuffer->StringWidth(*fText, fromOffset,
4148				numBytes, font);
4149		} else {
4150#endif
4151			const char* text = fText->GetString(fromOffset, &numBytes);
4152			result += font->StringWidth(text, numBytes);
4153
4154#if USE_WIDTHBUFFER
4155		}
4156#endif
4157
4158		fromOffset += numBytes;
4159		length -= numBytes;
4160	}
4161
4162	if (_ascent != NULL)
4163		*_ascent = maxAscent;
4164	if (_descent != NULL)
4165		*_descent = maxDescent;
4166
4167	return result;
4168}
4169
4170
4171//!	Calculate the actual tab width for the given location.
4172float
4173BTextView::_ActualTabWidth(float location) const
4174{
4175	float tabWidth = fTabWidth - fmod(location, fTabWidth);
4176	if (round(tabWidth) == 0)
4177		tabWidth = fTabWidth;
4178
4179	return tabWidth;
4180}
4181
4182
4183void
4184BTextView::_DoInsertText(const char* text, int32 length, int32 offset,
4185	const text_run_array* runs)
4186{
4187	_CancelInputMethod();
4188
4189	if (TextLength() + length > MaxBytes())
4190		return;
4191
4192	if (fSelStart != fSelEnd)
4193		Select(fSelStart, fSelStart);
4194
4195	const int32 textLength = TextLength();
4196	if (offset > textLength)
4197		offset = textLength;
4198
4199	// copy data into buffer
4200	InsertText(text, length, offset, runs);
4201
4202	// recalc line breaks and draw the text
4203	_Refresh(offset, offset + length, false);
4204}
4205
4206
4207void
4208BTextView::_DoDeleteText(int32 fromOffset, int32 toOffset)
4209{
4210	CALLED();
4211}
4212
4213
4214void
4215BTextView::_DrawLine(BView* view, const int32 &lineNum,
4216	const int32 &startOffset, const bool &erase, BRect &eraseRect,
4217	BRegion &inputRegion)
4218{
4219	STELine* line = (*fLines)[lineNum];
4220	float startLeft = fTextRect.left;
4221	if (startOffset != -1) {
4222		if (ByteAt(startOffset) == B_ENTER) {
4223			// StartOffset is a newline
4224			startLeft = PointAt(line->offset).x;
4225		} else
4226			startLeft = PointAt(startOffset).x;
4227	}
4228	else if (fAlignment != B_ALIGN_LEFT) {
4229		float alignmentOffset = fTextRect.Width() - LineWidth(lineNum);
4230		if (fAlignment == B_ALIGN_CENTER)
4231			alignmentOffset /= 2;
4232		startLeft = fTextRect.left + alignmentOffset;
4233	}
4234
4235	int32 length = (line + 1)->offset;
4236	if (startOffset != -1)
4237		length -= startOffset;
4238	else
4239		length -= line->offset;
4240
4241	// DrawString() chokes if you draw a newline
4242	if (ByteAt((line + 1)->offset - 1) == B_ENTER)
4243		length--;
4244
4245	view->MovePenTo(startLeft, line->origin + line->ascent + fTextRect.top + 1);
4246
4247	if (erase) {
4248		eraseRect.top = line->origin + fTextRect.top;
4249		eraseRect.bottom = (line + 1)->origin + fTextRect.top;
4250		view->FillRect(eraseRect, B_SOLID_LOW);
4251	}
4252
4253	// do we have any text to draw?
4254	if (length <= 0)
4255		return;
4256
4257	bool foundTab = false;
4258	int32 tabChars = 0;
4259	int32 numTabs = 0;
4260	int32 offset = startOffset != -1 ? startOffset : line->offset;
4261	const BFont* font = NULL;
4262	const rgb_color* color = NULL;
4263	int32 numBytes;
4264	drawing_mode defaultTextRenderingMode = DrawingMode();
4265	// iterate through each style on this line
4266	while ((numBytes = fStyles->Iterate(offset, length, fInline, &font,
4267			&color)) != 0) {
4268		view->SetFont(font);
4269		view->SetHighColor(*color);
4270
4271		tabChars = min_c(numBytes, length);
4272		do {
4273			foundTab = fText->FindChar(B_TAB, offset, &tabChars);
4274			if (foundTab) {
4275				do {
4276					numTabs++;
4277					if (ByteAt(offset + tabChars + numTabs) != B_TAB)
4278						break;
4279				} while ((tabChars + numTabs) < numBytes);
4280			}
4281
4282			drawing_mode textRenderingMode = defaultTextRenderingMode;
4283
4284			if (inputRegion.CountRects() > 0
4285				&& ((offset <= fInline->Offset()
4286					&& fInline->Offset() < offset + tabChars)
4287				|| (fInline->Offset() <= offset
4288					&& offset < fInline->Offset() + fInline->Length()))) {
4289
4290				textRenderingMode = B_OP_OVER;
4291
4292				BRegion textRegion;
4293				GetTextRegion(offset, offset + length, &textRegion);
4294
4295				textRegion.IntersectWith(&inputRegion);
4296				view->PushState();
4297
4298				// Highlight in blue the inputted text
4299				view->SetHighColor(kBlueInputColor);
4300				view->FillRect(textRegion.Frame());
4301
4302				// Highlight in red the selected part
4303				if (fInline->SelectionLength() > 0) {
4304					BRegion selectedRegion;
4305					GetTextRegion(fInline->Offset()
4306						+ fInline->SelectionOffset(), fInline->Offset()
4307						+ fInline->SelectionOffset()
4308						+ fInline->SelectionLength(), &selectedRegion);
4309
4310					textRegion.IntersectWith(&selectedRegion);
4311
4312					view->SetHighColor(kRedInputColor);
4313					view->FillRect(textRegion.Frame());
4314				}
4315
4316				view->PopState();
4317			}
4318
4319			int32 returnedBytes = tabChars;
4320			const char* stringToDraw = fText->GetString(offset, &returnedBytes);
4321			view->SetDrawingMode(textRenderingMode);
4322			view->DrawString(stringToDraw, returnedBytes);
4323			if (foundTab) {
4324				float penPos = PenLocation().x - fTextRect.left;
4325				float tabWidth = _ActualTabWidth(penPos);
4326				if (numTabs > 1)
4327					tabWidth += ((numTabs - 1) * fTabWidth);
4328
4329				view->MovePenBy(tabWidth, 0.0);
4330				tabChars += numTabs;
4331			}
4332
4333			offset += tabChars;
4334			length -= tabChars;
4335			numBytes -= tabChars;
4336			tabChars = min_c(numBytes, length);
4337			numTabs = 0;
4338		} while (foundTab && tabChars > 0);
4339	}
4340}
4341
4342
4343void
4344BTextView::_DrawLines(int32 startLine, int32 endLine, int32 startOffset,
4345	bool erase)
4346{
4347	if (!Window())
4348		return;
4349
4350	// clip the text
4351	BRect textRect(fTextRect);
4352	float minWidth
4353		= Bounds().Width() - fLayoutData->leftInset - fLayoutData->rightInset;
4354	if (textRect.Width() < minWidth)
4355		textRect.right = textRect.left + minWidth;
4356	BRect clipRect = Bounds() & textRect;
4357	clipRect.InsetBy(-1, -1);
4358
4359	BRegion newClip;
4360	newClip.Set(clipRect);
4361	ConstrainClippingRegion(&newClip);
4362
4363	// set the low color to the view color so that
4364	// drawing to a non-white background will work
4365	SetLowColor(ViewColor());
4366
4367	BView* view = NULL;
4368	if (fOffscreen == NULL)
4369		view = this;
4370	else {
4371		fOffscreen->Lock();
4372		view = fOffscreen->ChildAt(0);
4373		view->SetLowColor(ViewColor());
4374		view->FillRect(view->Bounds(), B_SOLID_LOW);
4375	}
4376
4377	long maxLine = fLines->NumLines() - 1;
4378	if (startLine < 0)
4379		startLine = 0;
4380	if (endLine > maxLine)
4381		endLine = maxLine;
4382
4383	// TODO: See if we can avoid this
4384	if (fAlignment != B_ALIGN_LEFT)
4385		erase = true;
4386
4387	BRect eraseRect = clipRect;
4388	int32 startEraseLine = startLine;
4389	STELine* line = (*fLines)[startLine];
4390
4391	if (erase && startOffset != -1 && fAlignment == B_ALIGN_LEFT) {
4392		// erase only to the right of startOffset
4393		startEraseLine++;
4394		int32 startErase = startOffset;
4395
4396		BPoint erasePoint = PointAt(startErase);
4397		eraseRect.left = erasePoint.x;
4398		eraseRect.top = erasePoint.y;
4399		eraseRect.bottom = (line + 1)->origin + fTextRect.top;
4400
4401		view->FillRect(eraseRect, B_SOLID_LOW);
4402
4403		eraseRect = clipRect;
4404	}
4405
4406	BRegion inputRegion;
4407	if (fInline != NULL && fInline->IsActive()) {
4408		GetTextRegion(fInline->Offset(), fInline->Offset() + fInline->Length(),
4409			&inputRegion);
4410	}
4411
4412	//BPoint leftTop(startLeft, line->origin);
4413	for (int32 lineNum = startLine; lineNum <= endLine; lineNum++) {
4414		const bool eraseThisLine = erase && lineNum >= startEraseLine;
4415		_DrawLine(view, lineNum, startOffset, eraseThisLine, eraseRect,
4416			inputRegion);
4417		startOffset = -1;
4418			// Set this to -1 so the next iteration will use the line offset
4419	}
4420
4421	// draw the caret/hilite the selection
4422	if (fActive) {
4423		if (fSelStart != fSelEnd) {
4424			if (fSelectable)
4425				Highlight(fSelStart, fSelEnd);
4426		} else {
4427			if (fCaretVisible)
4428				_DrawCaret(fSelStart, true);
4429		}
4430	}
4431
4432	if (fOffscreen != NULL) {
4433		view->Sync();
4434		/*BPoint penLocation = view->PenLocation();
4435		BRect drawRect(leftTop.x, leftTop.y, penLocation.x, penLocation.y);
4436		DrawBitmap(fOffscreen, drawRect, drawRect);*/
4437		fOffscreen->Unlock();
4438	}
4439
4440	ConstrainClippingRegion(NULL);
4441}
4442
4443
4444void
4445BTextView::_RequestDrawLines(int32 startLine, int32 endLine)
4446{
4447	if (!Window())
4448		return;
4449
4450	long maxLine = fLines->NumLines() - 1;
4451
4452	STELine* from = (*fLines)[startLine];
4453	STELine* to = endLine == maxLine ? NULL : (*fLines)[endLine + 1];
4454	BRect invalidRect(Bounds().left, from->origin + fTextRect.top,
4455		Bounds().right,
4456		to != NULL ? to->origin + fTextRect.top : fTextRect.bottom);
4457	Invalidate(invalidRect);
4458	Window()->UpdateIfNeeded();
4459}
4460
4461
4462void
4463BTextView::_DrawCaret(int32 offset, bool visible)
4464{
4465	float lineHeight;
4466	BPoint caretPoint = PointAt(offset, &lineHeight);
4467	caretPoint.x = min_c(caretPoint.x, fTextRect.right);
4468
4469	BRect caretRect;
4470	caretRect.left = caretRect.right = caretPoint.x;
4471	caretRect.top = caretPoint.y;
4472	caretRect.bottom = caretPoint.y + lineHeight - 1;
4473
4474	if (visible)
4475		InvertRect(caretRect);
4476	else
4477		Invalidate(caretRect);
4478}
4479
4480
4481inline void
4482BTextView::_ShowCaret()
4483{
4484	if (fActive && !fCaretVisible && fEditable && fSelStart == fSelEnd)
4485		_InvertCaret();
4486}
4487
4488
4489inline void
4490BTextView::_HideCaret()
4491{
4492	if (fCaretVisible && fSelStart == fSelEnd)
4493		_InvertCaret();
4494}
4495
4496
4497//!	Hides the caret if it is being shown, and if it's hidden, shows it.
4498void
4499BTextView::_InvertCaret()
4500{
4501	fCaretVisible = !fCaretVisible;
4502	_DrawCaret(fSelStart, fCaretVisible);
4503	fCaretTime = system_time();
4504}
4505
4506
4507/*!	Place the dragging caret at the given offset.
4508
4509	\param offset The offset (zero based within the object's text) where to
4510	       place the dragging caret. If it's -1, hide the caret.
4511*/
4512void
4513BTextView::_DragCaret(int32 offset)
4514{
4515	// does the caret need to move?
4516	if (offset == fDragOffset)
4517		return;
4518
4519	// hide the previous drag caret
4520	if (fDragOffset != -1)
4521		_DrawCaret(fDragOffset, false);
4522
4523	// do we have a new location?
4524	if (offset != -1) {
4525		if (fActive) {
4526			// ignore if offset is within active selection
4527			if (offset >= fSelStart && offset <= fSelEnd) {
4528				fDragOffset = -1;
4529				return;
4530			}
4531		}
4532
4533		_DrawCaret(offset, true);
4534	}
4535
4536	fDragOffset = offset;
4537}
4538
4539
4540void
4541BTextView::_StopMouseTracking()
4542{
4543	delete fTrackingMouse;
4544	fTrackingMouse = NULL;
4545}
4546
4547
4548bool
4549BTextView::_PerformMouseUp(BPoint where)
4550{
4551	if (fTrackingMouse == NULL)
4552		return false;
4553
4554	if (fTrackingMouse->selectionRect.IsValid())
4555		Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset);
4556
4557	_StopMouseTracking();
4558	// adjust cursor if necessary
4559	_TrackMouse(where, NULL, true);
4560
4561	return true;
4562}
4563
4564
4565bool
4566BTextView::_PerformMouseMoved(BPoint where, uint32 code)
4567{
4568	fWhere = where;
4569
4570	if (fTrackingMouse == NULL)
4571		return false;
4572
4573	int32 currentOffset = OffsetAt(where);
4574	if (fTrackingMouse->selectionRect.IsValid()) {
4575		// we are tracking the mouse for drag action, if the mouse has moved
4576		// to another index or more than three pixels from where it was clicked,
4577		// we initiate a drag now:
4578		if (currentOffset != fTrackingMouse->clickOffset
4579			|| fabs(fTrackingMouse->where.x - where.x) > 3
4580			|| fabs(fTrackingMouse->where.y - where.y) > 3) {
4581			_StopMouseTracking();
4582			_InitiateDrag();
4583			return true;
4584		}
4585		return false;
4586	}
4587
4588	switch (fClickCount) {
4589		case 3:
4590			// triple click, extend selection linewise
4591			if (currentOffset <= fTrackingMouse->anchor) {
4592				fTrackingMouse->selStart
4593					= (*fLines)[_LineAt(currentOffset)]->offset;
4594				fTrackingMouse->selEnd = fTrackingMouse->shiftDown
4595					? fSelEnd
4596					: (*fLines)[_LineAt(fTrackingMouse->anchor) + 1]->offset;
4597			} else {
4598				fTrackingMouse->selStart
4599					= fTrackingMouse->shiftDown
4600						? fSelStart
4601						: (*fLines)[_LineAt(fTrackingMouse->anchor)]->offset;
4602				fTrackingMouse->selEnd
4603					= (*fLines)[_LineAt(currentOffset) + 1]->offset;
4604			}
4605			break;
4606
4607		case 2:
4608			// double click, extend selection wordwise
4609			if (currentOffset <= fTrackingMouse->anchor) {
4610				fTrackingMouse->selStart = _PreviousWordBoundary(currentOffset);
4611				fTrackingMouse->selEnd
4612					= fTrackingMouse->shiftDown
4613						? fSelEnd
4614						: _NextWordBoundary(fTrackingMouse->anchor);
4615			} else {
4616				fTrackingMouse->selStart
4617					= fTrackingMouse->shiftDown
4618						? fSelStart
4619						: _PreviousWordBoundary(fTrackingMouse->anchor);
4620				fTrackingMouse->selEnd = _NextWordBoundary(currentOffset);
4621			}
4622			break;
4623
4624		default:
4625			// new click, extend selection char by char
4626			if (currentOffset <= fTrackingMouse->anchor) {
4627				fTrackingMouse->selStart = currentOffset;
4628				fTrackingMouse->selEnd
4629					= fTrackingMouse->shiftDown
4630						? fSelEnd : fTrackingMouse->anchor;
4631			} else {
4632				fTrackingMouse->selStart
4633					= fTrackingMouse->shiftDown
4634						? fSelStart : fTrackingMouse->anchor;
4635				fTrackingMouse->selEnd = currentOffset;
4636			}
4637			break;
4638	}
4639
4640	// position caret to follow the direction of the selection
4641	if (fTrackingMouse->selEnd != fSelEnd)
4642		fCaretOffset = fTrackingMouse->selEnd;
4643	else if (fTrackingMouse->selStart != fSelStart)
4644		fCaretOffset = fTrackingMouse->selStart;
4645
4646	Select(fTrackingMouse->selStart, fTrackingMouse->selEnd);
4647	_TrackMouse(where, NULL);
4648
4649	return true;
4650}
4651
4652
4653/*!	Tracks the mouse position, doing special actions like changing the
4654	view cursor.
4655
4656	\param where The point where the mouse has moved.
4657	\param message The dragging message, if there is any.
4658	\param force Passed as second parameter of SetViewCursor()
4659*/
4660void
4661BTextView::_TrackMouse(BPoint where, const BMessage* message, bool force)
4662{
4663	BRegion textRegion;
4664	GetTextRegion(fSelStart, fSelEnd, &textRegion);
4665
4666	if (message && AcceptsDrop(message))
4667		_TrackDrag(where);
4668	else if ((fSelectable || fEditable)
4669		&& (fTrackingMouse != NULL || !textRegion.Contains(where))) {
4670		SetViewCursor(B_CURSOR_I_BEAM, force);
4671	} else
4672		SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force);
4673}
4674
4675
4676//!	Tracks the mouse position when the user is dragging some data.
4677void
4678BTextView::_TrackDrag(BPoint where)
4679{
4680	CALLED();
4681	if (Bounds().Contains(where))
4682		_DragCaret(OffsetAt(where));
4683}
4684
4685
4686//!	Initiates a drag operation.
4687void
4688BTextView::_InitiateDrag()
4689{
4690	BMessage dragMessage(B_MIME_DATA);
4691	BBitmap* dragBitmap = NULL;
4692	BPoint bitmapPoint;
4693	BHandler* dragHandler = NULL;
4694
4695	GetDragParameters(&dragMessage, &dragBitmap, &bitmapPoint, &dragHandler);
4696	SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
4697
4698	if (dragBitmap != NULL)
4699		DragMessage(&dragMessage, dragBitmap, bitmapPoint, dragHandler);
4700	else {
4701		BRegion region;
4702		GetTextRegion(fSelStart, fSelEnd, &region);
4703		BRect bounds = Bounds();
4704		BRect dragRect = region.Frame();
4705		if (!bounds.Contains(dragRect))
4706			dragRect = bounds & dragRect;
4707
4708		DragMessage(&dragMessage, dragRect, dragHandler);
4709	}
4710
4711	BMessenger messenger(this);
4712	BMessage message(_DISPOSE_DRAG_);
4713	fDragRunner = new (nothrow) BMessageRunner(messenger, &message, 100000);
4714}
4715
4716
4717//!	Handles when some data is dropped on the view.
4718bool
4719BTextView::_MessageDropped(BMessage* message, BPoint where, BPoint offset)
4720{
4721	ASSERT(message);
4722
4723	void* from = NULL;
4724	bool internalDrop = false;
4725	if (message->FindPointer("be:originator", &from) == B_OK
4726			&& from == this && fSelEnd != fSelStart)
4727		internalDrop = true;
4728
4729	_DragCaret(-1);
4730
4731	delete fDragRunner;
4732	fDragRunner = NULL;
4733
4734	_TrackMouse(where, NULL);
4735
4736	// are we sure we like this message?
4737	if (!AcceptsDrop(message))
4738		return false;
4739
4740	int32 dropOffset = OffsetAt(where);
4741	if (dropOffset > TextLength())
4742		dropOffset = TextLength();
4743
4744	// if this view initiated the drag, move instead of copy
4745	if (internalDrop) {
4746		// dropping onto itself?
4747		if (dropOffset >= fSelStart && dropOffset <= fSelEnd)
4748			return true;
4749	}
4750
4751	ssize_t dataLength = 0;
4752	const char* text = NULL;
4753	entry_ref ref;
4754	if (message->FindData("text/plain", B_MIME_TYPE, (const void**)&text,
4755			&dataLength) == B_OK) {
4756		text_run_array* runArray = NULL;
4757		ssize_t runLength = 0;
4758		if (fStylable) {
4759			message->FindData("application/x-vnd.Be-text_run_array",
4760				B_MIME_TYPE, (const void**)&runArray, &runLength);
4761		}
4762
4763		_FilterDisallowedChars((char*)text, dataLength, runArray);
4764
4765		if (dataLength < 1) {
4766			beep();
4767			return true;
4768		}
4769
4770		if (fUndo) {
4771			delete fUndo;
4772			fUndo = new DropUndoBuffer(this, text, dataLength, runArray,
4773				runLength, dropOffset, internalDrop);
4774		}
4775
4776		if (internalDrop) {
4777			if (dropOffset > fSelEnd)
4778				dropOffset -= dataLength;
4779			Delete();
4780		}
4781
4782		Insert(dropOffset, text, dataLength, runArray);
4783	}
4784
4785	return true;
4786}
4787
4788
4789void
4790BTextView::_PerformAutoScrolling()
4791{
4792	// Scroll the view a bit if mouse is outside the view bounds
4793	BRect bounds = Bounds();
4794	BPoint scrollBy(B_ORIGIN);
4795
4796	// R5 does a pretty soft auto-scroll, we try to do the same by
4797	// simply scrolling the distance between cursor and border
4798	if (fWhere.x > bounds.right) {
4799		scrollBy.x = fWhere.x - bounds.right;
4800	} else if (fWhere.x < bounds.left) {
4801		scrollBy.x = fWhere.x - bounds.left; // negative value
4802	}
4803
4804	// prevent from scrolling out of view
4805	if (scrollBy.x != 0.0) {
4806		float rightMax = floorf(fTextRect.right + fLayoutData->rightInset);
4807		if (bounds.right + scrollBy.x > rightMax)
4808			scrollBy.x = rightMax - bounds.right;
4809		if (bounds.left + scrollBy.x < 0)
4810			scrollBy.x = -bounds.left;
4811	}
4812
4813	if (CountLines() > 1) {
4814		// scroll in Y only if multiple lines!
4815		if (fWhere.y > bounds.bottom) {
4816			scrollBy.y = fWhere.y - bounds.bottom;
4817		} else if (fWhere.y < bounds.top) {
4818			scrollBy.y = fWhere.y - bounds.top; // negative value
4819		}
4820
4821		// prevent from scrolling out of view
4822		if (scrollBy.y != 0.0) {
4823			float bottomMax = floorf(fTextRect.bottom
4824				+ fLayoutData->bottomInset);
4825			if (bounds.bottom + scrollBy.y > bottomMax)
4826				scrollBy.y = bottomMax - bounds.bottom;
4827			if (bounds.top + scrollBy.y < 0)
4828				scrollBy.y = -bounds.top;
4829		}
4830	}
4831
4832	if (scrollBy != B_ORIGIN)
4833		ScrollBy(scrollBy.x, scrollBy.y);
4834}
4835
4836
4837//!	Updates the scrollbars associated with the object (if any).
4838void
4839BTextView::_UpdateScrollbars()
4840{
4841	BRect bounds(Bounds());
4842	BScrollBar* horizontalScrollBar = ScrollBar(B_HORIZONTAL);
4843 	BScrollBar* verticalScrollBar = ScrollBar(B_VERTICAL);
4844
4845	// do we have a horizontal scroll bar?
4846	if (horizontalScrollBar != NULL) {
4847		long viewWidth = bounds.IntegerWidth();
4848		long dataWidth = (long)ceilf(fTextRect.IntegerWidth()
4849			+ fLayoutData->leftInset + fLayoutData->rightInset);
4850
4851		long maxRange = dataWidth - viewWidth;
4852		maxRange = max_c(maxRange, 0);
4853
4854		horizontalScrollBar->SetRange(0, (float)maxRange);
4855		horizontalScrollBar->SetProportion((float)viewWidth / (float)dataWidth);
4856		horizontalScrollBar->SetSteps(kHorizontalScrollBarStep, dataWidth / 10);
4857	}
4858
4859	// how about a vertical scroll bar?
4860	if (verticalScrollBar != NULL) {
4861		long viewHeight = bounds.IntegerHeight();
4862		long dataHeight = (long)ceilf(fTextRect.IntegerHeight()
4863			+ fLayoutData->topInset + fLayoutData->bottomInset);
4864
4865		long maxRange = dataHeight - viewHeight;
4866		maxRange = max_c(maxRange, 0);
4867
4868		verticalScrollBar->SetRange(0, maxRange);
4869		verticalScrollBar->SetProportion((float)viewHeight / (float)dataHeight);
4870		verticalScrollBar->SetSteps(kVerticalScrollBarStep, viewHeight);
4871	}
4872}
4873
4874
4875//!	Scrolls by the given offsets
4876void
4877BTextView::_ScrollBy(float horizontal, float vertical)
4878{
4879	BRect bounds = Bounds();
4880	_ScrollTo(bounds.left + horizontal, bounds.top + vertical);
4881}
4882
4883
4884//!	Scrolls to the given position, making sure not to scroll out of bounds.
4885void
4886BTextView::_ScrollTo(float x, float y)
4887{
4888	BRect bounds = Bounds();
4889	long viewWidth = bounds.IntegerWidth();
4890	long viewHeight = bounds.IntegerHeight();
4891
4892	if (x > fTextRect.right - viewWidth)
4893		x = fTextRect.right - viewWidth;
4894	if (x < 0.0)
4895		x = 0.0;
4896
4897	if (y > fTextRect.bottom + fLayoutData->bottomInset - viewHeight)
4898		y = fTextRect.bottom + fLayoutData->bottomInset - viewHeight;
4899	if (y < 0.0)
4900		y = 0.0;
4901
4902	ScrollTo(x, y);
4903}
4904
4905
4906//!	Autoresizes the view to fit the contained text.
4907void
4908BTextView::_AutoResize(bool redraw)
4909{
4910	if (!fResizable)
4911		return;
4912
4913	BRect bounds = Bounds();
4914	float oldWidth = bounds.Width();
4915	float newWidth = ceilf(fLayoutData->leftInset + fTextRect.Width()
4916		+ fLayoutData->rightInset);
4917
4918	if (fContainerView != NULL) {
4919		// NOTE: This container view thing is only used by Tracker.
4920		// move container view if not left aligned
4921		if (fAlignment == B_ALIGN_CENTER) {
4922			if (fmod(ceilf(newWidth - oldWidth), 2.0) != 0.0)
4923				newWidth += 1;
4924			fContainerView->MoveBy(ceilf(oldWidth - newWidth) / 2, 0);
4925		} else if (fAlignment == B_ALIGN_RIGHT) {
4926			fContainerView->MoveBy(ceilf(oldWidth - newWidth), 0);
4927		}
4928		// resize container view
4929		fContainerView->ResizeBy(ceilf(newWidth - oldWidth), 0);
4930	}
4931
4932
4933	if (redraw)
4934		_RequestDrawLines(0, 0);
4935
4936	// erase any potential left over outside the text rect
4937	// (can only be on right hand side)
4938	BRect dirty(fTextRect.right + 1, fTextRect.top, bounds.right,
4939		fTextRect.bottom);
4940	if (dirty.IsValid()) {
4941		SetLowColor(ViewColor());
4942		FillRect(dirty, B_SOLID_LOW);
4943	}
4944}
4945
4946
4947//!	Creates a new offscreen BBitmap with an associated BView.
4948void
4949BTextView::_NewOffscreen(float padding)
4950{
4951	if (fOffscreen != NULL)
4952		_DeleteOffscreen();
4953
4954#if USE_DOUBLEBUFFERING
4955	BRect bitmapRect(0, 0, fTextRect.Width() + padding, fTextRect.Height());
4956	fOffscreen = new BBitmap(bitmapRect, fColorSpace, true, false);
4957	if (fOffscreen != NULL && fOffscreen->Lock()) {
4958		BView* bufferView = new BView(bitmapRect, "drawing view", 0, 0);
4959		fOffscreen->AddChild(bufferView);
4960		fOffscreen->Unlock();
4961	}
4962#endif
4963}
4964
4965
4966//!	Deletes the textview's offscreen bitmap, if any.
4967void
4968BTextView::_DeleteOffscreen()
4969{
4970	if (fOffscreen != NULL && fOffscreen->Lock()) {
4971		delete fOffscreen;
4972		fOffscreen = NULL;
4973	}
4974}
4975
4976
4977/*!	Creates a new offscreen bitmap, highlight the selection, and set the
4978	cursor to \c B_CURSOR_I_BEAM.
4979*/
4980void
4981BTextView::_Activate()
4982{
4983	fActive = true;
4984
4985	// Create a new offscreen BBitmap
4986	_NewOffscreen();
4987
4988	if (fSelStart != fSelEnd) {
4989		if (fSelectable)
4990			Highlight(fSelStart, fSelEnd);
4991	} else
4992		_ShowCaret();
4993
4994	BPoint where;
4995	uint32 buttons;
4996	GetMouse(&where, &buttons, false);
4997	if (Bounds().Contains(where))
4998		_TrackMouse(where, NULL);
4999
5000	if (Window() != NULL) {
5001		BMessage* message;
5002
5003		if (!Window()->HasShortcut(B_LEFT_ARROW, B_COMMAND_KEY)
5004			&& !Window()->HasShortcut(B_RIGHT_ARROW, B_COMMAND_KEY)) {
5005			message = new BMessage(kMsgNavigateArrow);
5006			message->AddInt32("key", B_LEFT_ARROW);
5007			message->AddInt32("modifiers", B_COMMAND_KEY);
5008			Window()->AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, message, this);
5009
5010			message = new BMessage(kMsgNavigateArrow);
5011			message->AddInt32("key", B_RIGHT_ARROW);
5012			message->AddInt32("modifiers", B_COMMAND_KEY);
5013			Window()->AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, message, this);
5014
5015			fInstalledNavigateCommandWordwiseShortcuts = true;
5016		}
5017		if (!Window()->HasShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY)
5018			&& !Window()->HasShortcut(B_RIGHT_ARROW,
5019				B_COMMAND_KEY | B_SHIFT_KEY)) {
5020			message = new BMessage(kMsgNavigateArrow);
5021			message->AddInt32("key", B_LEFT_ARROW);
5022			message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
5023			Window()->AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY,
5024				message, this);
5025
5026			message = new BMessage(kMsgNavigateArrow);
5027			message->AddInt32("key", B_RIGHT_ARROW);
5028			message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
5029			Window()->AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY,
5030				message, this);
5031
5032			fInstalledSelectCommandWordwiseShortcuts = true;
5033		}
5034		if (!Window()->HasShortcut(B_DELETE, B_COMMAND_KEY)
5035			&& !Window()->HasShortcut(B_BACKSPACE, B_COMMAND_KEY)) {
5036			message = new BMessage(kMsgRemoveWord);
5037			message->AddInt32("key", B_DELETE);
5038			message->AddInt32("modifiers", B_COMMAND_KEY);
5039			Window()->AddShortcut(B_DELETE, B_COMMAND_KEY, message, this);
5040
5041			message = new BMessage(kMsgRemoveWord);
5042			message->AddInt32("key", B_BACKSPACE);
5043			message->AddInt32("modifiers", B_COMMAND_KEY);
5044			Window()->AddShortcut(B_BACKSPACE, B_COMMAND_KEY, message, this);
5045
5046			fInstalledRemoveCommandWordwiseShortcuts = true;
5047		}
5048
5049		if (!Window()->HasShortcut(B_LEFT_ARROW, B_OPTION_KEY)
5050			&& !Window()->HasShortcut(B_RIGHT_ARROW, B_OPTION_KEY)) {
5051			message = new BMessage(kMsgNavigateArrow);
5052			message->AddInt32("key", B_LEFT_ARROW);
5053			message->AddInt32("modifiers", B_OPTION_KEY);
5054			Window()->AddShortcut(B_LEFT_ARROW, B_OPTION_KEY, message, this);
5055
5056			message = new BMessage(kMsgNavigateArrow);
5057			message->AddInt32("key", B_RIGHT_ARROW);
5058			message->AddInt32("modifiers", B_OPTION_KEY);
5059			Window()->AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY, message, this);
5060
5061			fInstalledNavigateOptionWordwiseShortcuts = true;
5062		}
5063		if (!Window()->HasShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY)
5064			&& !Window()->HasShortcut(B_RIGHT_ARROW,
5065				B_OPTION_KEY | B_SHIFT_KEY)) {
5066			message = new BMessage(kMsgNavigateArrow);
5067			message->AddInt32("key", B_LEFT_ARROW);
5068			message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
5069			Window()->AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
5070				message, this);
5071
5072			message = new BMessage(kMsgNavigateArrow);
5073			message->AddInt32("key", B_RIGHT_ARROW);
5074			message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
5075			Window()->AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
5076				message, this);
5077
5078			fInstalledSelectOptionWordwiseShortcuts = true;
5079		}
5080		if (!Window()->HasShortcut(B_DELETE, B_OPTION_KEY)
5081			&& !Window()->HasShortcut(B_BACKSPACE, B_OPTION_KEY)) {
5082			message = new BMessage(kMsgRemoveWord);
5083			message->AddInt32("key", B_DELETE);
5084			message->AddInt32("modifiers", B_OPTION_KEY);
5085			Window()->AddShortcut(B_DELETE, B_OPTION_KEY, message, this);
5086
5087			message = new BMessage(kMsgRemoveWord);
5088			message->AddInt32("key", B_BACKSPACE);
5089			message->AddInt32("modifiers", B_OPTION_KEY);
5090			Window()->AddShortcut(B_BACKSPACE, B_OPTION_KEY, message, this);
5091
5092			fInstalledRemoveOptionWordwiseShortcuts = true;
5093		}
5094
5095		if (!Window()->HasShortcut(B_UP_ARROW, B_OPTION_KEY)
5096			&& !Window()->HasShortcut(B_DOWN_ARROW, B_OPTION_KEY)) {
5097			message = new BMessage(kMsgNavigateArrow);
5098			message->AddInt32("key", B_UP_ARROW);
5099			message->AddInt32("modifiers", B_OPTION_KEY);
5100			Window()->AddShortcut(B_UP_ARROW, B_OPTION_KEY, message, this);
5101
5102			message = new BMessage(kMsgNavigateArrow);
5103			message->AddInt32("key", B_DOWN_ARROW);
5104			message->AddInt32("modifiers", B_OPTION_KEY);
5105			Window()->AddShortcut(B_DOWN_ARROW, B_OPTION_KEY, message, this);
5106
5107			fInstalledNavigateOptionLinewiseShortcuts = true;
5108		}
5109		if (!Window()->HasShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY)
5110			&& !Window()->HasShortcut(B_DOWN_ARROW,
5111				B_OPTION_KEY | B_SHIFT_KEY)) {
5112			message = new BMessage(kMsgNavigateArrow);
5113			message->AddInt32("key", B_UP_ARROW);
5114			message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
5115			Window()->AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
5116				message, this);
5117
5118			message = new BMessage(kMsgNavigateArrow);
5119			message->AddInt32("key", B_DOWN_ARROW);
5120			message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
5121			Window()->AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
5122				message, this);
5123
5124			fInstalledSelectOptionLinewiseShortcuts = true;
5125		}
5126
5127		if (!Window()->HasShortcut(B_HOME, B_COMMAND_KEY)
5128			&& !Window()->HasShortcut(B_END, B_COMMAND_KEY)) {
5129			message = new BMessage(kMsgNavigatePage);
5130			message->AddInt32("key", B_HOME);
5131			message->AddInt32("modifiers", B_COMMAND_KEY);
5132			Window()->AddShortcut(B_HOME, B_COMMAND_KEY, message, this);
5133
5134			message = new BMessage(kMsgNavigatePage);
5135			message->AddInt32("key", B_END);
5136			message->AddInt32("modifiers", B_COMMAND_KEY);
5137			Window()->AddShortcut(B_END, B_COMMAND_KEY, message, this);
5138
5139			fInstalledNavigateHomeEndDocwiseShortcuts = true;
5140		}
5141		if (!Window()->HasShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY)
5142			&& !Window()->HasShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY)) {
5143			message = new BMessage(kMsgNavigatePage);
5144			message->AddInt32("key", B_HOME);
5145			message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
5146			Window()->AddShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY,
5147				message, this);
5148
5149			message = new BMessage(kMsgNavigatePage);
5150			message->AddInt32("key", B_END);
5151			message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
5152			Window()->AddShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY,
5153				message, this);
5154
5155			fInstalledSelectHomeEndDocwiseShortcuts = true;
5156		}
5157	}
5158}
5159
5160
5161//!	Unhilights the selection, set the cursor to \c B_CURSOR_SYSTEM_DEFAULT.
5162void
5163BTextView::_Deactivate()
5164{
5165	fActive = false;
5166
5167	_CancelInputMethod();
5168	_DeleteOffscreen();
5169
5170	if (fSelStart != fSelEnd) {
5171		if (fSelectable)
5172			Highlight(fSelStart, fSelEnd);
5173	} else
5174		_HideCaret();
5175
5176	if (Window() != NULL) {
5177		if (fInstalledNavigateCommandWordwiseShortcuts) {
5178			Window()->RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY);
5179			Window()->RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY);
5180			fInstalledNavigateCommandWordwiseShortcuts = false;
5181		}
5182		if (fInstalledSelectCommandWordwiseShortcuts) {
5183			Window()->RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY);
5184			Window()->RemoveShortcut(B_RIGHT_ARROW,
5185				B_COMMAND_KEY | B_SHIFT_KEY);
5186			fInstalledSelectCommandWordwiseShortcuts = false;
5187		}
5188		if (fInstalledRemoveCommandWordwiseShortcuts) {
5189			Window()->RemoveShortcut(B_DELETE, B_COMMAND_KEY);
5190			Window()->RemoveShortcut(B_BACKSPACE, B_COMMAND_KEY);
5191			fInstalledRemoveCommandWordwiseShortcuts = false;
5192		}
5193
5194		if (fInstalledNavigateOptionWordwiseShortcuts) {
5195			Window()->RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY);
5196			Window()->RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY);
5197			fInstalledNavigateOptionWordwiseShortcuts = false;
5198		}
5199		if (fInstalledSelectOptionWordwiseShortcuts) {
5200			Window()->RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
5201			Window()->RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
5202			fInstalledSelectOptionWordwiseShortcuts = false;
5203		}
5204		if (fInstalledRemoveOptionWordwiseShortcuts) {
5205			Window()->RemoveShortcut(B_DELETE, B_OPTION_KEY);
5206			Window()->RemoveShortcut(B_BACKSPACE, B_OPTION_KEY);
5207			fInstalledRemoveOptionWordwiseShortcuts = false;
5208		}
5209
5210		if (fInstalledNavigateOptionLinewiseShortcuts) {
5211			Window()->RemoveShortcut(B_UP_ARROW, B_OPTION_KEY);
5212			Window()->RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY);
5213			fInstalledNavigateOptionLinewiseShortcuts = false;
5214		}
5215		if (fInstalledSelectOptionLinewiseShortcuts) {
5216			Window()->RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
5217			Window()->RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
5218			fInstalledSelectOptionLinewiseShortcuts = false;
5219		}
5220
5221		if (fInstalledNavigateHomeEndDocwiseShortcuts) {
5222			Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY);
5223			Window()->RemoveShortcut(B_END, B_COMMAND_KEY);
5224			fInstalledNavigateHomeEndDocwiseShortcuts = false;
5225		}
5226		if (fInstalledSelectHomeEndDocwiseShortcuts) {
5227			Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY);
5228			Window()->RemoveShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY);
5229			fInstalledSelectHomeEndDocwiseShortcuts = false;
5230		}
5231	}
5232}
5233
5234
5235/*!	Changes the passed in font to be displayable by the object.
5236
5237	Set font rotation to 0, removes any font flag, set font spacing
5238	to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8.
5239*/
5240void
5241BTextView::_NormalizeFont(BFont* font)
5242{
5243	if (font) {
5244		font->SetRotation(0.0f);
5245		font->SetFlags(0);
5246		font->SetSpacing(B_BITMAP_SPACING);
5247		font->SetEncoding(B_UNICODE_UTF8);
5248	}
5249}
5250
5251
5252void
5253BTextView::_SetRunArray(int32 startOffset, int32 endOffset,
5254	const text_run_array* runs)
5255{
5256	const int32 numStyles = runs->count;
5257	if (numStyles > 0) {
5258		const text_run* theRun = &runs->runs[0];
5259		for (int32 index = 0; index < numStyles; index++) {
5260			int32 fromOffset = theRun->offset + startOffset;
5261			int32 toOffset = endOffset;
5262			if (index + 1 < numStyles) {
5263				toOffset = (theRun + 1)->offset + startOffset;
5264				toOffset = (toOffset > endOffset) ? endOffset : toOffset;
5265			}
5266
5267			_ApplyStyleRange(fromOffset, toOffset, B_FONT_ALL, &theRun->font,
5268				&theRun->color, false);
5269
5270			theRun++;
5271		}
5272		fStyles->InvalidateNullStyle();
5273	}
5274}
5275
5276
5277/*!	Returns the character class of the character at the given offset.
5278
5279	\param offset The offset where the wanted character can be found.
5280
5281	\return A value which represents the character's classification.
5282*/
5283uint32
5284BTextView::_CharClassification(int32 offset) const
5285{
5286	// TODO: Should check against a list of characters containing also
5287	// japanese word breakers.
5288	// And what about other languages ? Isn't there a better way to check
5289	// for separator characters ?
5290	// Andrew suggested to have a look at UnicodeBlockObject.h
5291	switch (fText->RealCharAt(offset)) {
5292		case '\0':
5293			return CHAR_CLASS_END_OF_TEXT;
5294
5295		case B_SPACE:
5296		case B_TAB:
5297		case B_ENTER:
5298			return CHAR_CLASS_WHITESPACE;
5299
5300		case '=':
5301		case '+':
5302		case '@':
5303		case '#':
5304		case '$':
5305		case '%':
5306		case '^':
5307		case '&':
5308		case '*':
5309		case '\\':
5310		case '|':
5311		case '<':
5312		case '>':
5313		case '/':
5314		case '~':
5315			return CHAR_CLASS_GRAPHICAL;
5316
5317		case '\'':
5318		case '"':
5319			return CHAR_CLASS_QUOTE;
5320
5321		case ',':
5322		case '.':
5323		case '?':
5324		case '!':
5325		case ';':
5326		case ':':
5327		case '-':
5328			return CHAR_CLASS_PUNCTUATION;
5329
5330		case '(':
5331		case '[':
5332		case '{':
5333			return CHAR_CLASS_PARENS_OPEN;
5334
5335		case ')':
5336		case ']':
5337		case '}':
5338			return CHAR_CLASS_PARENS_CLOSE;
5339
5340		default:
5341			return CHAR_CLASS_DEFAULT;
5342	}
5343}
5344
5345
5346/*!	Returns the offset of the next UTF-8 character.
5347
5348	\param offset The offset where to start looking.
5349
5350	\return The offset of the next UTF-8 character.
5351*/
5352int32
5353BTextView::_NextInitialByte(int32 offset) const
5354{
5355	if (offset >= fText->Length())
5356		return offset;
5357
5358	for (++offset; (ByteAt(offset) & 0xC0) == 0x80; ++offset)
5359		;
5360
5361	return offset;
5362}
5363
5364
5365/*!	Returns the offset of the previous UTF-8 character.
5366
5367	\param offset The offset where to start looking.
5368
5369	\return The offset of the previous UTF-8 character.
5370*/
5371int32
5372BTextView::_PreviousInitialByte(int32 offset) const
5373{
5374	if (offset <= 0)
5375		return 0;
5376
5377	int32 count = 6;
5378
5379	for (--offset; offset > 0 && count; --offset, --count) {
5380		if ((ByteAt(offset) & 0xC0) != 0x80)
5381			break;
5382	}
5383
5384	return count ? offset : 0;
5385}
5386
5387
5388bool
5389BTextView::_GetProperty(BMessage* specifier, int32 form, const char* property,
5390	BMessage* reply)
5391{
5392	CALLED();
5393	if (strcmp(property, "selection") == 0) {
5394		reply->what = B_REPLY;
5395		reply->AddInt32("result", fSelStart);
5396		reply->AddInt32("result", fSelEnd);
5397		reply->AddInt32("error", B_OK);
5398
5399		return true;
5400	} else if (strcmp(property, "Text") == 0) {
5401		if (IsTypingHidden()) {
5402			// Do not allow stealing passwords via scripting
5403			beep();
5404			return false;
5405		}
5406
5407		int32 index, range;
5408		specifier->FindInt32("index", &index);
5409		specifier->FindInt32("range", &range);
5410
5411		char* buffer = new char[range + 1];
5412		GetText(index, range, buffer);
5413
5414		reply->what = B_REPLY;
5415		reply->AddString("result", buffer);
5416		reply->AddInt32("error", B_OK);
5417
5418		delete[] buffer;
5419
5420		return true;
5421	} else if (strcmp(property, "text_run_array") == 0)
5422		return false;
5423
5424	return false;
5425}
5426
5427
5428bool
5429BTextView::_SetProperty(BMessage* specifier, int32 form, const char* property,
5430	BMessage* reply)
5431{
5432	CALLED();
5433	if (strcmp(property, "selection") == 0) {
5434		int32 index, range;
5435
5436		specifier->FindInt32("index", &index);
5437		specifier->FindInt32("range", &range);
5438
5439		Select(index, index + range);
5440
5441		reply->what = B_REPLY;
5442		reply->AddInt32("error", B_OK);
5443
5444		return true;
5445	} else if (strcmp(property, "Text") == 0) {
5446		int32 index, range;
5447		specifier->FindInt32("index", &index);
5448		specifier->FindInt32("range", &range);
5449
5450		const char* buffer = NULL;
5451		if (specifier->FindString("data", &buffer) == B_OK)
5452			InsertText(buffer, range, index, NULL);
5453		else
5454			DeleteText(index, range);
5455
5456		reply->what = B_REPLY;
5457		reply->AddInt32("error", B_OK);
5458
5459		return true;
5460	} else if (strcmp(property, "text_run_array") == 0)
5461		return false;
5462
5463	return false;
5464}
5465
5466
5467bool
5468BTextView::_CountProperties(BMessage* specifier, int32 form,
5469	const char* property, BMessage* reply)
5470{
5471	CALLED();
5472	if (strcmp(property, "Text") == 0) {
5473		reply->what = B_REPLY;
5474		reply->AddInt32("result", TextLength());
5475		reply->AddInt32("error", B_OK);
5476		return true;
5477	}
5478
5479	return false;
5480}
5481
5482
5483//!	Called when the object receives a \c B_INPUT_METHOD_CHANGED message.
5484void
5485BTextView::_HandleInputMethodChanged(BMessage* message)
5486{
5487	// TODO: block input if not editable (Andrew)
5488	ASSERT(fInline != NULL);
5489
5490	const char* string = NULL;
5491	if (message->FindString("be:string", &string) < B_OK || string == NULL)
5492		return;
5493
5494	_HideCaret();
5495
5496	if (IsFocus())
5497		be_app->ObscureCursor();
5498
5499	// If we find the "be:confirmed" boolean (and the boolean is true),
5500	// it means it's over for now, so the current InlineInput object
5501	// should become inactive. We will probably receive a
5502	// B_INPUT_METHOD_STOPPED message after this one.
5503	bool confirmed;
5504	if (message->FindBool("be:confirmed", &confirmed) != B_OK)
5505		confirmed = false;
5506
5507	// Delete the previously inserted text (if any)
5508	if (fInline->IsActive()) {
5509		const int32 oldOffset = fInline->Offset();
5510		DeleteText(oldOffset, oldOffset + fInline->Length());
5511		if (confirmed)
5512			fInline->SetActive(false);
5513		fCaretOffset = fSelStart = fSelEnd = oldOffset;
5514	}
5515
5516	const int32 stringLen = strlen(string);
5517
5518	fInline->SetOffset(fSelStart);
5519	fInline->SetLength(stringLen);
5520	fInline->ResetClauses();
5521
5522	if (!confirmed && !fInline->IsActive())
5523		fInline->SetActive(true);
5524
5525	// Get the clauses, and pass them to the InlineInput object
5526	// TODO: Find out if what we did it's ok, currently we don't consider
5527	// clauses at all, while the bebook says we should; though the visual
5528	// effect we obtained seems correct. Weird.
5529	int32 clauseCount = 0;
5530	int32 clauseStart;
5531	int32 clauseEnd;
5532	while (message->FindInt32("be:clause_start", clauseCount, &clauseStart)
5533			== B_OK
5534		&& message->FindInt32("be:clause_end", clauseCount, &clauseEnd)
5535			== B_OK) {
5536		if (!fInline->AddClause(clauseStart, clauseEnd))
5537			break;
5538		clauseCount++;
5539	}
5540
5541	if (confirmed) {
5542		_Refresh(fSelStart, fSelEnd, true);
5543		_ShowCaret();
5544
5545		// now we need to feed ourselves the individual characters as if the
5546		// user would have pressed them now - this lets KeyDown() pick out all
5547		// the special characters like B_BACKSPACE, cursor keys and the like:
5548		const char* currPos = string;
5549		const char* prevPos = currPos;
5550		while (*currPos != '\0') {
5551			if ((*currPos & 0xC0) == 0xC0) {
5552				// found the start of an UTF-8 char, we collect while it lasts
5553				++currPos;
5554				while ((*currPos & 0xC0) == 0x80)
5555					++currPos;
5556			} else if ((*currPos & 0xC0) == 0x80) {
5557				// illegal: character starts with utf-8 intermediate byte,
5558				// skip it
5559				prevPos = ++currPos;
5560			} else {
5561				// single byte character/code, just feed that
5562				++currPos;
5563			}
5564			KeyDown(prevPos, currPos - prevPos);
5565			prevPos = currPos;
5566		}
5567
5568		_Refresh(fSelStart, fSelEnd, true);
5569	} else {
5570		// temporarily show transient state of inline input
5571		int32 selectionStart = 0;
5572		int32 selectionEnd = 0;
5573		message->FindInt32("be:selection", 0, &selectionStart);
5574		message->FindInt32("be:selection", 1, &selectionEnd);
5575
5576		fInline->SetSelectionOffset(selectionStart);
5577		fInline->SetSelectionLength(selectionEnd - selectionStart);
5578
5579		const int32 inlineOffset = fInline->Offset();
5580		InsertText(string, stringLen, fSelStart, NULL);
5581
5582		_Refresh(inlineOffset, fSelEnd, true);
5583		_ShowCaret();
5584	}
5585
5586}
5587
5588
5589/*!	Called when the object receives a \c B_INPUT_METHOD_LOCATION_REQUEST
5590	message.
5591*/
5592void
5593BTextView::_HandleInputMethodLocationRequest()
5594{
5595	ASSERT(fInline != NULL);
5596
5597	int32 offset = fInline->Offset();
5598	const int32 limit = offset + fInline->Length();
5599
5600	BMessage message(B_INPUT_METHOD_EVENT);
5601	message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
5602
5603	// Add the location of the UTF8 characters
5604	while (offset < limit) {
5605		float height;
5606		BPoint where = PointAt(offset, &height);
5607		ConvertToScreen(&where);
5608
5609		message.AddPoint("be:location_reply", where);
5610		message.AddFloat("be:height_reply", height);
5611
5612		offset = _NextInitialByte(offset);
5613	}
5614
5615	fInline->Method()->SendMessage(&message);
5616}
5617
5618
5619//!	Tells the Input Server method add-on to stop the current transaction.
5620void
5621BTextView::_CancelInputMethod()
5622{
5623	if (!fInline)
5624		return;
5625
5626	InlineInput* inlineInput = fInline;
5627	fInline = NULL;
5628
5629	if (inlineInput->IsActive() && Window()) {
5630		_Refresh(inlineInput->Offset(), fText->Length() - inlineInput->Offset(),
5631			false);
5632
5633		BMessage message(B_INPUT_METHOD_EVENT);
5634		message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
5635		inlineInput->Method()->SendMessage(&message);
5636	}
5637
5638	delete inlineInput;
5639}
5640
5641
5642/*!	Returns the line number of the character at the given \a offset.
5643
5644	\note This will never return the last line (use LineAt() if you
5645	      need to be correct about that.) N.B.
5646
5647	\param offset The offset of the wanted character.
5648
5649	\return The line number of the character at the given \a offset as an int32.
5650*/
5651int32
5652BTextView::_LineAt(int32 offset) const
5653{
5654	return fLines->OffsetToLine(offset);
5655}
5656
5657
5658/*!	Returns the line number that the given \a point is on.
5659
5660	\note This will never return the last line (use LineAt() if you
5661	      need to be correct about that.) N.B.
5662
5663	\param point The \a point the get the line number of.
5664
5665	\return The line number of the given \a point as an int32.
5666*/
5667int32
5668BTextView::_LineAt(const BPoint& point) const
5669{
5670	return fLines->PixelToLine(point.y - fTextRect.top);
5671}
5672
5673
5674/*!	Returns whether or not the given \a offset is on the empty line at the end
5675	of the buffer.
5676*/
5677bool
5678BTextView::_IsOnEmptyLastLine(int32 offset) const
5679{
5680	return (offset == TextLength() && offset > 0
5681		&& fText->RealCharAt(offset - 1) == B_ENTER);
5682}
5683
5684
5685void
5686BTextView::_ApplyStyleRange(int32 fromOffset, int32 toOffset, uint32 mode,
5687	const BFont* font, const rgb_color* color, bool syncNullStyle)
5688{
5689	BFont normalized;
5690		// Declared before the if so it stays allocated until the call to
5691		// SetStyleRange
5692	if (font != NULL) {
5693		// if a font has been given, normalize it
5694		normalized = *font;
5695		_NormalizeFont(&normalized);
5696		font = &normalized;
5697	}
5698
5699	if (!fStylable) {
5700		// always apply font and color to full range for non-stylable textviews
5701		fromOffset = 0;
5702		toOffset = fText->Length();
5703	}
5704
5705	if (syncNullStyle)
5706		fStyles->SyncNullStyle(fromOffset);
5707
5708	fStyles->SetStyleRange(fromOffset, toOffset, fText->Length(), mode,
5709		font, color);
5710}
5711
5712
5713float
5714BTextView::_NullStyleHeight() const
5715{
5716	const BFont* font = NULL;
5717	fStyles->GetNullStyle(&font, NULL);
5718
5719	font_height fontHeight;
5720	font->GetHeight(&fontHeight);
5721	return ceilf(fontHeight.ascent + fontHeight.descent + 1);
5722}
5723
5724
5725void
5726BTextView::_ShowContextMenu(BPoint where)
5727{
5728	bool isRedo;
5729	undo_state state = UndoState(&isRedo);
5730	bool isUndo = state != B_UNDO_UNAVAILABLE && !isRedo;
5731
5732	int32 start;
5733	int32 finish;
5734	GetSelection(&start, &finish);
5735
5736	bool canEdit = IsEditable();
5737	int32 length = TextLength();
5738
5739	BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
5740
5741	BLayoutBuilder::Menu<>(menu)
5742		.AddItem(TRANSLATE("Undo"), B_UNDO/*, 'Z'*/)
5743			.SetEnabled(canEdit && isUndo)
5744		.AddItem(TRANSLATE("Redo"), B_UNDO/*, 'Z', B_SHIFT_KEY*/)
5745			.SetEnabled(canEdit && isRedo)
5746		.AddSeparator()
5747		.AddItem(TRANSLATE("Cut"), B_CUT, 'X')
5748			.SetEnabled(canEdit && start != finish)
5749		.AddItem(TRANSLATE("Copy"), B_COPY, 'C')
5750			.SetEnabled(start != finish)
5751		.AddItem(TRANSLATE("Paste"), B_PASTE, 'V')
5752			.SetEnabled(canEdit && be_clipboard->SystemCount() > 0)
5753		.AddSeparator()
5754		.AddItem(TRANSLATE("Select all"), B_SELECT_ALL, 'A')
5755			.SetEnabled(!(start == 0 && finish == length))
5756	;
5757
5758	menu->SetTargetForItems(this);
5759	ConvertToScreen(&where);
5760	menu->Go(where, true, true,	true);
5761}
5762
5763
5764void
5765BTextView::_FilterDisallowedChars(char* text, ssize_t& length,
5766	text_run_array* runArray)
5767{
5768	if (!fDisallowedChars)
5769		return;
5770
5771	if (fDisallowedChars->IsEmpty() || !text)
5772		return;
5773
5774	ssize_t stringIndex = 0;
5775	if (runArray) {
5776		ssize_t remNext = 0;
5777
5778		for (int i = 0; i < runArray->count; i++) {
5779			runArray->runs[i].offset -= remNext;
5780			while (stringIndex < runArray->runs[i].offset
5781				&& stringIndex < length) {
5782				if (fDisallowedChars->HasItem(
5783					reinterpret_cast<void*>(text[stringIndex]))) {
5784					memmove(text + stringIndex, text + stringIndex + 1,
5785						length - stringIndex - 1);
5786					length--;
5787					runArray->runs[i].offset--;
5788					remNext++;
5789				} else
5790					stringIndex++;
5791			}
5792		}
5793	}
5794
5795	while (stringIndex < length) {
5796		if (fDisallowedChars->HasItem(
5797			reinterpret_cast<void*>(text[stringIndex]))) {
5798			memmove(text + stringIndex, text + stringIndex + 1,
5799				length - stringIndex - 1);
5800			length--;
5801		} else
5802			stringIndex++;
5803	}
5804}
5805
5806
5807// #pragma mark - BTextView::TextTrackState
5808
5809
5810BTextView::TextTrackState::TextTrackState(BMessenger messenger)
5811	:
5812	clickOffset(0),
5813	shiftDown(false),
5814	anchor(0),
5815	selStart(0),
5816	selEnd(0),
5817	fRunner(NULL)
5818{
5819	BMessage message(_PING_);
5820	const bigtime_t scrollSpeed = 25 * 1000;	// 40 scroll steps per second
5821	fRunner = new (nothrow) BMessageRunner(messenger, &message, scrollSpeed);
5822}
5823
5824
5825BTextView::TextTrackState::~TextTrackState()
5826{
5827	delete fRunner;
5828}
5829
5830
5831void
5832BTextView::TextTrackState::SimulateMouseMovement(BTextView* textView)
5833{
5834	BPoint where;
5835	uint32 buttons;
5836	// When the mouse cursor is still and outside the textview,
5837	// no B_MOUSE_MOVED message are sent, obviously. But scrolling
5838	// has to work neverthless, so we "fake" a MouseMoved() call here.
5839	textView->GetMouse(&where, &buttons);
5840	textView->_PerformMouseMoved(where, B_INSIDE_VIEW);
5841}
5842
5843
5844// #pragma mark - Binary ABI compat
5845
5846
5847extern "C" void
5848B_IF_GCC_2(InvalidateLayout__9BTextViewb,  _ZN9BTextView16InvalidateLayoutEb)(
5849	BTextView* view, bool descendants)
5850{
5851	perform_data_layout_invalidated data;
5852	data.descendants = descendants;
5853
5854	view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
5855}
5856