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