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