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