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