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