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