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