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