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