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