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