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