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