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