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