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