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