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