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