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