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