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