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