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