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