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