xref: /haiku/src/kits/interface/TextView.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
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 || fSelectable) && 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 			if (!fSelectable)
2384 				_HideCaret();
2385 			_CancelInputMethod();
2386 		}
2387 	}
2388 }
2389 
2390 
2391 bool
2392 BTextView::IsEditable() const
2393 {
2394 	return fEditable;
2395 }
2396 
2397 
2398 void
2399 BTextView::SetWordWrap(bool wrap)
2400 {
2401 	if (wrap == fWrap)
2402 		return;
2403 
2404 	bool updateOnScreen = fActive && Window() != NULL;
2405 	if (updateOnScreen) {
2406 		// hide the caret, unhilite the selection
2407 		if (fSelStart != fSelEnd) {
2408 			if (fSelectable)
2409 				Highlight(fSelStart, fSelEnd);
2410 		} else
2411 			_HideCaret();
2412 	}
2413 
2414 	BRect savedBounds = Bounds();
2415 
2416 	fWrap = wrap;
2417 	if (wrap)
2418 		_ResetTextRect(); // calls _Refresh
2419 	else
2420 		_Refresh(0, fText->Length());
2421 
2422 	if (fEditable || fSelectable)
2423 		ScrollToOffset(fCaretOffset);
2424 
2425 	// redraw text rect and update scroll bars if bounds have changed
2426 	if (Bounds() != savedBounds)
2427 		FrameResized(Bounds().Width(), Bounds().Height());
2428 
2429 	if (updateOnScreen) {
2430 		// show the caret, hilite the selection
2431 		if (fSelStart != fSelEnd) {
2432 			if (fSelectable)
2433 				Highlight(fSelStart, fSelEnd);
2434 		} else
2435 			_ShowCaret();
2436 	}
2437 }
2438 
2439 
2440 bool
2441 BTextView::DoesWordWrap() const
2442 {
2443 	return fWrap;
2444 }
2445 
2446 
2447 void
2448 BTextView::SetMaxBytes(int32 max)
2449 {
2450 	const int32 textLength = fText->Length();
2451 	fMaxBytes = max;
2452 
2453 	if (fMaxBytes < textLength) {
2454 		int32 offset = fMaxBytes;
2455 		// Delete the text after fMaxBytes, but
2456 		// respect multibyte characters boundaries.
2457 		const int32 previousInitial = _PreviousInitialByte(offset);
2458 		if (_NextInitialByte(previousInitial) != offset)
2459 			offset = previousInitial;
2460 
2461 		Delete(offset, textLength);
2462 	}
2463 }
2464 
2465 
2466 int32
2467 BTextView::MaxBytes() const
2468 {
2469 	return fMaxBytes;
2470 }
2471 
2472 
2473 void
2474 BTextView::DisallowChar(uint32 character)
2475 {
2476 	if (fDisallowedChars == NULL)
2477 		fDisallowedChars = new BList;
2478 	if (!fDisallowedChars->HasItem(reinterpret_cast<void*>(character)))
2479 		fDisallowedChars->AddItem(reinterpret_cast<void*>(character));
2480 }
2481 
2482 
2483 void
2484 BTextView::AllowChar(uint32 character)
2485 {
2486 	if (fDisallowedChars != NULL)
2487 		fDisallowedChars->RemoveItem(reinterpret_cast<void*>(character));
2488 }
2489 
2490 
2491 void
2492 BTextView::SetAlignment(alignment align)
2493 {
2494 	// Do a reality check
2495 	if (fAlignment != align &&
2496 			(align == B_ALIGN_LEFT ||
2497 			 align == B_ALIGN_RIGHT ||
2498 			 align == B_ALIGN_CENTER)) {
2499 		fAlignment = align;
2500 
2501 		// After setting new alignment, update the view/window
2502 		if (Window() != NULL) {
2503 			FrameResized(Bounds().Width(), Bounds().Height());
2504 				// text rect position and scroll bars may change
2505 			Invalidate();
2506 		}
2507 	}
2508 }
2509 
2510 
2511 alignment
2512 BTextView::Alignment() const
2513 {
2514 	return fAlignment;
2515 }
2516 
2517 
2518 void
2519 BTextView::SetAutoindent(bool state)
2520 {
2521 	fAutoindent = state;
2522 }
2523 
2524 
2525 bool
2526 BTextView::DoesAutoindent() const
2527 {
2528 	return fAutoindent;
2529 }
2530 
2531 
2532 void
2533 BTextView::SetColorSpace(color_space colors)
2534 {
2535 	if (colors != fColorSpace && fOffscreen) {
2536 		fColorSpace = colors;
2537 		_DeleteOffscreen();
2538 		_NewOffscreen();
2539 	}
2540 }
2541 
2542 
2543 color_space
2544 BTextView::ColorSpace() const
2545 {
2546 	return fColorSpace;
2547 }
2548 
2549 
2550 void
2551 BTextView::MakeResizable(bool resize, BView* resizeView)
2552 {
2553 	if (resize) {
2554 		fResizable = true;
2555 		fContainerView = resizeView;
2556 
2557 		// Wrapping mode and resizable mode can't live together
2558 		if (fWrap) {
2559 			fWrap = false;
2560 
2561 			if (fActive && Window() != NULL) {
2562 				if (fSelStart != fSelEnd) {
2563 					if (fSelectable)
2564 						Highlight(fSelStart, fSelEnd);
2565 				} else
2566 					_HideCaret();
2567 			}
2568 		}
2569 		// We need to reset the right inset, as otherwise the auto-resize would
2570 		// get confused about just how wide the textview needs to be.
2571 		// This seems to be an artifact of how Tracker creates the textview
2572 		// during a rename action.
2573 		fLayoutData->rightInset = fLayoutData->leftInset;
2574 	} else {
2575 		fResizable = false;
2576 		fContainerView = NULL;
2577 		if (fOffscreen)
2578 			_DeleteOffscreen();
2579 		_NewOffscreen();
2580 	}
2581 
2582 	_Refresh(0, fText->Length());
2583 }
2584 
2585 
2586 bool
2587 BTextView::IsResizable() const
2588 {
2589 	return fResizable;
2590 }
2591 
2592 
2593 void
2594 BTextView::SetDoesUndo(bool undo)
2595 {
2596 	if (undo && fUndo == NULL)
2597 		fUndo = new UndoBuffer(this, B_UNDO_UNAVAILABLE);
2598 	else if (!undo && fUndo != NULL) {
2599 		delete fUndo;
2600 		fUndo = NULL;
2601 	}
2602 }
2603 
2604 
2605 bool
2606 BTextView::DoesUndo() const
2607 {
2608 	return fUndo != NULL;
2609 }
2610 
2611 
2612 void
2613 BTextView::HideTyping(bool enabled)
2614 {
2615 	if (enabled)
2616 		Delete(0, fText->Length());
2617 
2618 	fText->SetPasswordMode(enabled);
2619 }
2620 
2621 
2622 bool
2623 BTextView::IsTypingHidden() const
2624 {
2625 	return fText->PasswordMode();
2626 }
2627 
2628 
2629 // #pragma mark - Size methods
2630 
2631 
2632 void
2633 BTextView::ResizeToPreferred()
2634 {
2635 	BView::ResizeToPreferred();
2636 }
2637 
2638 
2639 void
2640 BTextView::GetPreferredSize(float* _width, float* _height)
2641 {
2642 	CALLED();
2643 
2644 	_ValidateLayoutData();
2645 
2646 	if (_width) {
2647 		float width = Bounds().Width();
2648 		if (width < fLayoutData->min.width
2649 			|| (Flags() & B_SUPPORTS_LAYOUT) != 0) {
2650 			width = fLayoutData->min.width;
2651 		}
2652 		*_width = width;
2653 	}
2654 
2655 	if (_height) {
2656 		float height = Bounds().Height();
2657 		if (height < fLayoutData->min.height
2658 			|| (Flags() & B_SUPPORTS_LAYOUT) != 0) {
2659 			height = fLayoutData->min.height;
2660 		}
2661 		*_height = height;
2662 	}
2663 }
2664 
2665 
2666 BSize
2667 BTextView::MinSize()
2668 {
2669 	CALLED();
2670 
2671 	_ValidateLayoutData();
2672 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
2673 }
2674 
2675 
2676 BSize
2677 BTextView::MaxSize()
2678 {
2679 	CALLED();
2680 
2681 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
2682 		BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
2683 }
2684 
2685 
2686 BSize
2687 BTextView::PreferredSize()
2688 {
2689 	CALLED();
2690 
2691 	_ValidateLayoutData();
2692 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
2693 		fLayoutData->preferred);
2694 }
2695 
2696 
2697 bool
2698 BTextView::HasHeightForWidth()
2699 {
2700 	if (IsEditable())
2701 		return BView::HasHeightForWidth();
2702 
2703 	// When not editable, we assume that all text is supposed to be visible.
2704 	return true;
2705 }
2706 
2707 
2708 void
2709 BTextView::GetHeightForWidth(float width, float* min, float* max,
2710 	float* preferred)
2711 {
2712 	if (IsEditable()) {
2713 		BView::GetHeightForWidth(width, min, max, preferred);
2714 		return;
2715 	}
2716 
2717 	BRect saveTextRect = fTextRect;
2718 
2719 	fTextRect.right = fTextRect.left + width;
2720 
2721 	// If specific insets were set, reduce the width accordingly (this may result in more
2722 	// linebreaks being inserted)
2723 	if (fLayoutData->overridden) {
2724 		fTextRect.left += fLayoutData->leftInset;
2725 		fTextRect.right -= fLayoutData->rightInset;
2726 	}
2727 
2728 	int32 fromLine = _LineAt(0);
2729 	int32 toLine = _LineAt(fText->Length());
2730 	_RecalculateLineBreaks(&fromLine, &toLine);
2731 
2732 	// If specific insets were set, add the top and bottom margins to the returned preferred height
2733 	if (fLayoutData->overridden) {
2734 		fTextRect.top -= fLayoutData->topInset;
2735 		fTextRect.bottom += fLayoutData->bottomInset;
2736 	}
2737 
2738 	if (min != NULL)
2739 		*min = fTextRect.Height();
2740 	if (max != NULL)
2741 		*max = B_SIZE_UNLIMITED;
2742 	if (preferred != NULL)
2743 		*preferred = fTextRect.Height();
2744 
2745 	// Restore the text rect since we were not supposed to change it in this method.
2746 	// Unfortunately, we did change a few other things by calling _RecalculateLineBreaks, that are
2747 	// not so easily undone. However, we are likely to soon get resized to the new width and height
2748 	// computed here, and that will recompute the linebreaks and do a full _Refresh if needed.
2749 	fTextRect = saveTextRect;
2750 }
2751 
2752 
2753 //	#pragma mark - Layout methods
2754 
2755 
2756 void
2757 BTextView::LayoutInvalidated(bool descendants)
2758 {
2759 	CALLED();
2760 
2761 	fLayoutData->valid = false;
2762 }
2763 
2764 
2765 void
2766 BTextView::DoLayout()
2767 {
2768 	// Bail out, if we shan't do layout.
2769 	if (!(Flags() & B_SUPPORTS_LAYOUT))
2770 		return;
2771 
2772 	CALLED();
2773 
2774 	// If the user set a layout, we let the base class version call its
2775 	// hook.
2776 	if (GetLayout()) {
2777 		BView::DoLayout();
2778 		return;
2779 	}
2780 
2781 	_ValidateLayoutData();
2782 
2783 	// validate current size
2784 	BSize size(Bounds().Size());
2785 	if (size.width < fLayoutData->min.width)
2786 		size.width = fLayoutData->min.width;
2787 	if (size.height < fLayoutData->min.height)
2788 		size.height = fLayoutData->min.height;
2789 
2790 	_ResetTextRect();
2791 }
2792 
2793 
2794 void
2795 BTextView::_ValidateLayoutData()
2796 {
2797 	if (fLayoutData->valid)
2798 		return;
2799 
2800 	CALLED();
2801 
2802 	float lineHeight = ceilf(LineHeight(0));
2803 	TRACE("line height: %.2f\n", lineHeight);
2804 
2805 	// compute our minimal size
2806 	BSize min(lineHeight * 3, lineHeight);
2807 	min.width += fLayoutData->leftInset + fLayoutData->rightInset;
2808 	min.height += fLayoutData->topInset + fLayoutData->bottomInset;
2809 
2810 	fLayoutData->min = min;
2811 
2812 	// compute our preferred size
2813 	fLayoutData->preferred.height = fTextRect.Height()
2814 		+ fLayoutData->topInset + fLayoutData->bottomInset;
2815 
2816 	if (fWrap)
2817 		fLayoutData->preferred.width = min.width + 5 * lineHeight;
2818 	else {
2819 		float maxWidth = fLines->MaxWidth();
2820 		if (maxWidth < min.width)
2821 			maxWidth = min.width;
2822 
2823 		fLayoutData->preferred.width
2824 			= maxWidth + fLayoutData->leftInset + fLayoutData->rightInset;
2825 	}
2826 
2827 	fLayoutData->valid = true;
2828 	ResetLayoutInvalidation();
2829 
2830 	TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
2831 }
2832 
2833 
2834 //	#pragma mark -
2835 
2836 
2837 void
2838 BTextView::AllAttached()
2839 {
2840 	BView::AllAttached();
2841 }
2842 
2843 
2844 void
2845 BTextView::AllDetached()
2846 {
2847 	BView::AllDetached();
2848 }
2849 
2850 
2851 /* static */
2852 text_run_array*
2853 BTextView::AllocRunArray(int32 entryCount, int32* outSize)
2854 {
2855 	int32 size = sizeof(text_run_array) + (entryCount - 1) * sizeof(text_run);
2856 
2857 	text_run_array* runArray = (text_run_array*)calloc(size, 1);
2858 	if (runArray == NULL) {
2859 		if (outSize != NULL)
2860 			*outSize = 0;
2861 		return NULL;
2862 	}
2863 
2864 	runArray->count = entryCount;
2865 
2866 	// Call constructors explicitly as the text_run_array
2867 	// was allocated with malloc (and has to, for backwards
2868 	// compatibility)
2869 	for (int32 i = 0; i < runArray->count; i++)
2870 		new (&runArray->runs[i].font) BFont;
2871 
2872 	if (outSize != NULL)
2873 		*outSize = size;
2874 
2875 	return runArray;
2876 }
2877 
2878 
2879 /* static */
2880 text_run_array*
2881 BTextView::CopyRunArray(const text_run_array* orig, int32 countDelta)
2882 {
2883 	text_run_array* copy = AllocRunArray(countDelta, NULL);
2884 	if (copy != NULL) {
2885 		for (int32 i = 0; i < countDelta; i++) {
2886 			copy->runs[i].offset = orig->runs[i].offset;
2887 			copy->runs[i].font = orig->runs[i].font;
2888 			copy->runs[i].color = orig->runs[i].color;
2889 		}
2890 	}
2891 	return copy;
2892 }
2893 
2894 
2895 /* static */
2896 void
2897 BTextView::FreeRunArray(text_run_array* array)
2898 {
2899 	if (array == NULL)
2900 		return;
2901 
2902 	// Call destructors explicitly
2903 	for (int32 i = 0; i < array->count; i++)
2904 		array->runs[i].font.~BFont();
2905 
2906 	free(array);
2907 }
2908 
2909 
2910 /* static */
2911 void*
2912 BTextView::FlattenRunArray(const text_run_array* runArray, int32* _size)
2913 {
2914 	CALLED();
2915 	int32 size = sizeof(flattened_text_run_array) + (runArray->count - 1)
2916 		* sizeof(flattened_text_run);
2917 
2918 	flattened_text_run_array* array = (flattened_text_run_array*)malloc(size);
2919 	if (array == NULL) {
2920 		if (_size)
2921 			*_size = 0;
2922 		return NULL;
2923 	}
2924 
2925 	array->magic = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayMagic);
2926 	array->version = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayVersion);
2927 	array->count = B_HOST_TO_BENDIAN_INT32(runArray->count);
2928 
2929 	for (int32 i = 0; i < runArray->count; i++) {
2930 		array->styles[i].offset = B_HOST_TO_BENDIAN_INT32(
2931 			runArray->runs[i].offset);
2932 		runArray->runs[i].font.GetFamilyAndStyle(&array->styles[i].family,
2933 			&array->styles[i].style);
2934 		array->styles[i].size = B_HOST_TO_BENDIAN_FLOAT(
2935 			runArray->runs[i].font.Size());
2936 		array->styles[i].shear = B_HOST_TO_BENDIAN_FLOAT(
2937 			runArray->runs[i].font.Shear());
2938 		array->styles[i].face = B_HOST_TO_BENDIAN_INT16(
2939 			runArray->runs[i].font.Face());
2940 		array->styles[i].red = runArray->runs[i].color.red;
2941 		array->styles[i].green = runArray->runs[i].color.green;
2942 		array->styles[i].blue = runArray->runs[i].color.blue;
2943 		array->styles[i].alpha = 255;
2944 		array->styles[i]._reserved_ = 0;
2945 	}
2946 
2947 	if (_size)
2948 		*_size = size;
2949 
2950 	return array;
2951 }
2952 
2953 
2954 /* static */
2955 text_run_array*
2956 BTextView::UnflattenRunArray(const void* data, int32* _size)
2957 {
2958 	CALLED();
2959 	flattened_text_run_array* array = (flattened_text_run_array*)data;
2960 
2961 	if (B_BENDIAN_TO_HOST_INT32(array->magic) != kFlattenedTextRunArrayMagic
2962 		|| B_BENDIAN_TO_HOST_INT32(array->version)
2963 			!= kFlattenedTextRunArrayVersion) {
2964 		if (_size)
2965 			*_size = 0;
2966 
2967 		return NULL;
2968 	}
2969 
2970 	int32 count = B_BENDIAN_TO_HOST_INT32(array->count);
2971 
2972 	text_run_array* runArray = AllocRunArray(count, _size);
2973 	if (runArray == NULL)
2974 		return NULL;
2975 
2976 	for (int32 i = 0; i < count; i++) {
2977 		runArray->runs[i].offset = B_BENDIAN_TO_HOST_INT32(
2978 			array->styles[i].offset);
2979 
2980 		// Set family and style independently from each other, so that
2981 		// even if the family doesn't exist, we try to preserve the style
2982 		runArray->runs[i].font.SetFamilyAndStyle(array->styles[i].family, NULL);
2983 		runArray->runs[i].font.SetFamilyAndStyle(NULL, array->styles[i].style);
2984 
2985 		runArray->runs[i].font.SetSize(B_BENDIAN_TO_HOST_FLOAT(
2986 			array->styles[i].size));
2987 		runArray->runs[i].font.SetShear(B_BENDIAN_TO_HOST_FLOAT(
2988 			array->styles[i].shear));
2989 
2990 		uint16 face = B_BENDIAN_TO_HOST_INT16(array->styles[i].face);
2991 		if (face != B_REGULAR_FACE) {
2992 			// Be's version doesn't seem to set this correctly
2993 			runArray->runs[i].font.SetFace(face);
2994 		}
2995 
2996 		runArray->runs[i].color.red = array->styles[i].red;
2997 		runArray->runs[i].color.green = array->styles[i].green;
2998 		runArray->runs[i].color.blue = array->styles[i].blue;
2999 		runArray->runs[i].color.alpha = array->styles[i].alpha;
3000 	}
3001 
3002 	return runArray;
3003 }
3004 
3005 
3006 void
3007 BTextView::InsertText(const char* text, int32 length, int32 offset,
3008 	const text_run_array* runs)
3009 {
3010 	CALLED();
3011 
3012 	if (length < 0)
3013 		length = 0;
3014 
3015 	if (offset < 0)
3016 		offset = 0;
3017 	else if (offset > fText->Length())
3018 		offset = fText->Length();
3019 
3020 	if (length > 0) {
3021 		// add the text to the buffer
3022 		fText->InsertText(text, length, offset);
3023 
3024 		// update the start offsets of each line below offset
3025 		fLines->BumpOffset(length, _LineAt(offset) + 1);
3026 
3027 		// update the style runs
3028 		fStyles->BumpOffset(length, fStyles->OffsetToRun(offset - 1) + 1);
3029 
3030 		// offset the caret/selection, if the text was inserted before it
3031 		if (offset <= fSelEnd) {
3032 			fSelStart += length;
3033 			fCaretOffset = fSelEnd = fSelStart;
3034 		}
3035 	}
3036 
3037 	if (fStylable && runs != NULL)
3038 		_SetRunArray(offset, offset + length, runs);
3039 	else {
3040 		// apply null-style to inserted text
3041 		_ApplyStyleRange(offset, offset + length);
3042 	}
3043 }
3044 
3045 
3046 void
3047 BTextView::DeleteText(int32 fromOffset, int32 toOffset)
3048 {
3049 	CALLED();
3050 
3051 	if (fromOffset < 0)
3052 		fromOffset = 0;
3053 	else if (fromOffset > fText->Length())
3054 		fromOffset = fText->Length();
3055 
3056 	if (toOffset < 0)
3057 		toOffset = 0;
3058 	else if (toOffset > fText->Length())
3059 		toOffset = fText->Length();
3060 
3061 	if (fromOffset >= toOffset)
3062 		return;
3063 
3064 	// set nullStyle to style at beginning of range
3065 	fStyles->InvalidateNullStyle();
3066 	fStyles->SyncNullStyle(fromOffset);
3067 
3068 	// remove from the text buffer
3069 	fText->RemoveRange(fromOffset, toOffset);
3070 
3071 	// remove any lines that have been obliterated
3072 	fLines->RemoveLineRange(fromOffset, toOffset);
3073 
3074 	// remove any style runs that have been obliterated
3075 	fStyles->RemoveStyleRange(fromOffset, toOffset);
3076 
3077 	// adjust the selection accordingly, assumes fSelEnd >= fSelStart!
3078 	int32 range = toOffset - fromOffset;
3079 	if (fSelStart >= toOffset) {
3080 		// selection is behind the range that was removed
3081 		fSelStart -= range;
3082 		fSelEnd -= range;
3083 	} else if (fSelStart >= fromOffset && fSelEnd <= toOffset) {
3084 		// the selection is within the range that was removed
3085 		fSelStart = fSelEnd = fromOffset;
3086 	} else if (fSelStart >= fromOffset && fSelEnd > toOffset) {
3087 		// the selection starts within and ends after the range
3088 		// the remaining part is the part that was after the range
3089 		fSelStart = fromOffset;
3090 		fSelEnd = fromOffset + fSelEnd - toOffset;
3091 	} else if (fSelStart < fromOffset && fSelEnd < toOffset) {
3092 		// the selection starts before, but ends within the range
3093 		fSelEnd = fromOffset;
3094 	} else if (fSelStart < fromOffset && fSelEnd >= toOffset) {
3095 		// the selection starts before and ends after the range
3096 		fSelEnd -= range;
3097 	}
3098 }
3099 
3100 
3101 /*!	Undoes the last changes.
3102 
3103 	\param clipboard A \a clipboard to use for the undo operation.
3104 */
3105 void
3106 BTextView::Undo(BClipboard* clipboard)
3107 {
3108 	if (fUndo)
3109 		fUndo->Undo(clipboard);
3110 }
3111 
3112 
3113 undo_state
3114 BTextView::UndoState(bool* isRedo) const
3115 {
3116 	return fUndo == NULL ? B_UNDO_UNAVAILABLE : fUndo->State(isRedo);
3117 }
3118 
3119 
3120 //	#pragma mark - GetDragParameters() is protected
3121 
3122 
3123 void
3124 BTextView::GetDragParameters(BMessage* drag, BBitmap** bitmap, BPoint* point,
3125 	BHandler** handler)
3126 {
3127 	CALLED();
3128 	if (drag == NULL)
3129 		return;
3130 
3131 	// Add originator and action
3132 	drag->AddPointer("be:originator", this);
3133 	drag->AddInt32("be_actions", B_TRASH_TARGET);
3134 
3135 	// add the text
3136 	int32 numBytes = fSelEnd - fSelStart;
3137 	const char* text = fText->GetString(fSelStart, &numBytes);
3138 	drag->AddData("text/plain", B_MIME_TYPE, text, numBytes);
3139 
3140 	// add the corresponding styles
3141 	int32 size = 0;
3142 	text_run_array* styles = RunArray(fSelStart, fSelEnd, &size);
3143 
3144 	if (styles != NULL) {
3145 		drag->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
3146 			styles, size);
3147 
3148 		FreeRunArray(styles);
3149 	}
3150 
3151 	if (bitmap != NULL)
3152 		*bitmap = NULL;
3153 
3154 	if (handler != NULL)
3155 		*handler = NULL;
3156 }
3157 
3158 
3159 //	#pragma mark - FBC padding and forbidden methods
3160 
3161 
3162 void BTextView::_ReservedTextView3() {}
3163 void BTextView::_ReservedTextView4() {}
3164 void BTextView::_ReservedTextView5() {}
3165 void BTextView::_ReservedTextView6() {}
3166 void BTextView::_ReservedTextView7() {}
3167 void BTextView::_ReservedTextView8() {}
3168 void BTextView::_ReservedTextView9() {}
3169 void BTextView::_ReservedTextView10() {}
3170 void BTextView::_ReservedTextView11() {}
3171 void BTextView::_ReservedTextView12() {}
3172 
3173 
3174 // #pragma mark - Private methods
3175 
3176 
3177 /*!	Inits the BTextView object.
3178 
3179 	\param textRect The BTextView's text rect.
3180 	\param initialFont The font which the BTextView will use.
3181 	\param initialColor The initial color of the text.
3182 */
3183 void
3184 BTextView::_InitObject(BRect textRect, const BFont* initialFont,
3185 	const rgb_color* initialColor)
3186 {
3187 	BFont font;
3188 	if (initialFont == NULL)
3189 		GetFont(&font);
3190 	else
3191 		font = *initialFont;
3192 
3193 	_NormalizeFont(&font);
3194 
3195 	rgb_color documentTextColor = ui_color(B_DOCUMENT_TEXT_COLOR);
3196 
3197 	if (initialColor == NULL)
3198 		initialColor = &documentTextColor;
3199 
3200 	fText = new BPrivate::TextGapBuffer;
3201 	fLines = new LineBuffer;
3202 	fStyles = new StyleBuffer(&font, initialColor);
3203 
3204 	fInstalledNavigateCommandWordwiseShortcuts = false;
3205 	fInstalledNavigateOptionWordwiseShortcuts = false;
3206 	fInstalledNavigateOptionLinewiseShortcuts = false;
3207 	fInstalledNavigateHomeEndDocwiseShortcuts = false;
3208 
3209 	fInstalledSelectCommandWordwiseShortcuts = false;
3210 	fInstalledSelectOptionWordwiseShortcuts = false;
3211 	fInstalledSelectOptionLinewiseShortcuts = false;
3212 	fInstalledSelectHomeEndDocwiseShortcuts = false;
3213 
3214 	fInstalledRemoveCommandWordwiseShortcuts = false;
3215 	fInstalledRemoveOptionWordwiseShortcuts = false;
3216 
3217 	// We put these here instead of in the constructor initializer list
3218 	// to have less code duplication, and a single place where to do changes
3219 	// if needed.
3220 	fTextRect = textRect;
3221 		// NOTE: The only places where text rect is changed:
3222 		// * width and height are adjusted in _RecalculateLineBreaks(),
3223 		// text rect maintains constant insets, use SetInsets() to change.
3224 	fMinTextRectWidth = fTextRect.Width();
3225 		// see SetTextRect()
3226 	fSelStart = fSelEnd = 0;
3227 	fCaretVisible = false;
3228 	fCaretTime = 0;
3229 	fCaretOffset = 0;
3230 	fClickCount = 0;
3231 	fClickTime = 0;
3232 	fDragOffset = -1;
3233 	fCursor = 0;
3234 	fActive = false;
3235 	fStylable = false;
3236 	fTabWidth = 28.0;
3237 	fSelectable = true;
3238 	fEditable = true;
3239 	fWrap = true;
3240 	fMaxBytes = INT32_MAX;
3241 	fDisallowedChars = NULL;
3242 	fAlignment = B_ALIGN_LEFT;
3243 	fAutoindent = false;
3244 	fOffscreen = NULL;
3245 	fColorSpace = B_CMAP8;
3246 	fResizable = false;
3247 	fContainerView = NULL;
3248 	fUndo = NULL;
3249 	fInline = NULL;
3250 	fDragRunner = NULL;
3251 	fClickRunner = NULL;
3252 	fTrackingMouse = NULL;
3253 
3254 	fLayoutData = new LayoutData;
3255 	_UpdateInsets(textRect);
3256 
3257 	fLastClickOffset = -1;
3258 
3259 	SetDoesUndo(true);
3260 }
3261 
3262 
3263 //!	Handles when Backspace key is pressed.
3264 void
3265 BTextView::_HandleBackspace(int32 modifiers)
3266 {
3267 	if (!fEditable)
3268 		return;
3269 
3270 	if (modifiers < 0) {
3271 		BMessage* currentMessage = Window()->CurrentMessage();
3272 		if (currentMessage == NULL
3273 			|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
3274 			modifiers = 0;
3275 		}
3276 	}
3277 
3278 	bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
3279 	bool optionKeyDown  = (modifiers & B_OPTION_KEY)  != 0;
3280 	bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
3281 
3282 	if ((commandKeyDown || optionKeyDown) && !controlKeyDown) {
3283 		fSelStart = _PreviousWordStart(fCaretOffset - 1);
3284 		fSelEnd = fCaretOffset;
3285 	}
3286 
3287 	if (fUndo) {
3288 		TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo);
3289 		if (!undoBuffer) {
3290 			delete fUndo;
3291 			fUndo = undoBuffer = new TypingUndoBuffer(this);
3292 		}
3293 		undoBuffer->BackwardErase();
3294 	}
3295 
3296 	// we may draw twice, so turn updates off for now
3297 	if (Window() != NULL)
3298 		Window()->DisableUpdates();
3299 
3300 	if (fSelStart == fSelEnd) {
3301 		if (fSelStart != 0)
3302 			fSelStart = _PreviousInitialByte(fSelStart);
3303 	} else
3304 		Highlight(fSelStart, fSelEnd);
3305 
3306 	DeleteText(fSelStart, fSelEnd);
3307 	fCaretOffset = fSelEnd = fSelStart;
3308 
3309 	_Refresh(fSelStart, fSelEnd, fCaretOffset);
3310 
3311 	// turn updates back on
3312 	if (Window() != NULL)
3313 		Window()->EnableUpdates();
3314 }
3315 
3316 
3317 //!	Handles when an arrow key is pressed.
3318 void
3319 BTextView::_HandleArrowKey(uint32 arrowKey, int32 modifiers)
3320 {
3321 	// return if there's nowhere to go
3322 	if (fText->Length() == 0)
3323 		return;
3324 
3325 	int32 selStart = fSelStart;
3326 	int32 selEnd = fSelEnd;
3327 
3328 	if (modifiers < 0) {
3329 		BMessage* currentMessage = Window()->CurrentMessage();
3330 		if (currentMessage == NULL
3331 			|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
3332 			modifiers = 0;
3333 		}
3334 	}
3335 
3336 	bool shiftKeyDown   = (modifiers & B_SHIFT_KEY)   != 0;
3337 	bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
3338 	bool optionKeyDown  = (modifiers & B_OPTION_KEY)  != 0;
3339 	bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
3340 
3341 	int32 lastClickOffset = fCaretOffset;
3342 
3343 	switch (arrowKey) {
3344 		case B_LEFT_ARROW:
3345 			if (!fEditable && !fSelectable)
3346 				_ScrollBy(-1 * kHorizontalScrollBarStep, 0);
3347 			else if (fSelStart != fSelEnd && !shiftKeyDown)
3348 				fCaretOffset = fSelStart;
3349 			else {
3350 				if ((commandKeyDown || optionKeyDown) && !controlKeyDown)
3351 					fCaretOffset = _PreviousWordStart(fCaretOffset - 1);
3352 				else
3353 					fCaretOffset = _PreviousInitialByte(fCaretOffset);
3354 
3355 				if (shiftKeyDown && fCaretOffset != lastClickOffset) {
3356 					if (fCaretOffset < fSelStart) {
3357 						// extend selection to the left
3358 						selStart = fCaretOffset;
3359 						if (lastClickOffset > fSelStart) {
3360 							// caret has jumped across "anchor"
3361 							selEnd = fSelStart;
3362 						}
3363 					} else {
3364 						// shrink selection from the right
3365 						selEnd = fCaretOffset;
3366 					}
3367 				}
3368 			}
3369 			break;
3370 
3371 		case B_RIGHT_ARROW:
3372 			if (!fEditable && !fSelectable)
3373 				_ScrollBy(kHorizontalScrollBarStep, 0);
3374 			else if (fSelStart != fSelEnd && !shiftKeyDown)
3375 				fCaretOffset = fSelEnd;
3376 			else {
3377 				if ((commandKeyDown || optionKeyDown) && !controlKeyDown)
3378 					fCaretOffset = _NextWordEnd(fCaretOffset);
3379 				else
3380 					fCaretOffset = _NextInitialByte(fCaretOffset);
3381 
3382 				if (shiftKeyDown && fCaretOffset != lastClickOffset) {
3383 					if (fCaretOffset > fSelEnd) {
3384 						// extend selection to the right
3385 						selEnd = fCaretOffset;
3386 						if (lastClickOffset < fSelEnd) {
3387 							// caret has jumped across "anchor"
3388 							selStart = fSelEnd;
3389 						}
3390 					} else {
3391 						// shrink selection from the left
3392 						selStart = fCaretOffset;
3393 					}
3394 				}
3395 			}
3396 			break;
3397 
3398 		case B_UP_ARROW:
3399 		{
3400 			if (!fEditable && !fSelectable)
3401 				_ScrollBy(0, -1 * kVerticalScrollBarStep);
3402 			else if (fSelStart != fSelEnd && !shiftKeyDown)
3403 				fCaretOffset = fSelStart;
3404 			else {
3405 				if (optionKeyDown && !commandKeyDown && !controlKeyDown)
3406 					fCaretOffset = _PreviousLineStart(fCaretOffset);
3407 				else if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
3408 					_ScrollTo(0, 0);
3409 					fCaretOffset = 0;
3410 				} else {
3411 					float height;
3412 					BPoint point = PointAt(fCaretOffset, &height);
3413 					// find the caret position on the previous
3414 					// line by gently stepping onto this line
3415 					for (int i = 1; i <= height; i++) {
3416 						point.y--;
3417 						int32 offset = OffsetAt(point);
3418 						if (offset < fCaretOffset || i == height) {
3419 							fCaretOffset = offset;
3420 							break;
3421 						}
3422 					}
3423 				}
3424 
3425 				if (shiftKeyDown && fCaretOffset != lastClickOffset) {
3426 					if (fCaretOffset < fSelStart) {
3427 						// extend selection to the top
3428 						selStart = fCaretOffset;
3429 						if (lastClickOffset > fSelStart) {
3430 							// caret has jumped across "anchor"
3431 							selEnd = fSelStart;
3432 						}
3433 					} else {
3434 						// shrink selection from the bottom
3435 						selEnd = fCaretOffset;
3436 					}
3437 				}
3438 			}
3439 			break;
3440 		}
3441 
3442 		case B_DOWN_ARROW:
3443 		{
3444 			if (!fEditable && !fSelectable)
3445 				_ScrollBy(0, kVerticalScrollBarStep);
3446 			else if (fSelStart != fSelEnd && !shiftKeyDown)
3447 				fCaretOffset = fSelEnd;
3448 			else {
3449 				if (optionKeyDown && !commandKeyDown && !controlKeyDown)
3450 					fCaretOffset = _NextLineEnd(fCaretOffset);
3451 				else if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
3452 					_ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset);
3453 					fCaretOffset = fText->Length();
3454 				} else {
3455 					float height;
3456 					BPoint point = PointAt(fCaretOffset, &height);
3457 					point.y += height;
3458 					fCaretOffset = OffsetAt(point);
3459 				}
3460 
3461 				if (shiftKeyDown && fCaretOffset != lastClickOffset) {
3462 					if (fCaretOffset > fSelEnd) {
3463 						// extend selection to the bottom
3464 						selEnd = fCaretOffset;
3465 						if (lastClickOffset < fSelEnd) {
3466 							// caret has jumped across "anchor"
3467 							selStart = fSelEnd;
3468 						}
3469 					} else {
3470 						// shrink selection from the top
3471 						selStart = fCaretOffset;
3472 					}
3473 				}
3474 			}
3475 			break;
3476 		}
3477 	}
3478 
3479 	fStyles->InvalidateNullStyle();
3480 
3481 	if (fEditable || fSelectable) {
3482 		if (shiftKeyDown)
3483 			Select(selStart, selEnd);
3484 		else
3485 			Select(fCaretOffset, fCaretOffset);
3486 
3487 		// scroll if needed
3488 		ScrollToOffset(fCaretOffset);
3489 	}
3490 }
3491 
3492 
3493 //!	Handles when the Delete key is pressed.
3494 void
3495 BTextView::_HandleDelete(int32 modifiers)
3496 {
3497 	if (!fEditable)
3498 		return;
3499 
3500 	if (modifiers < 0) {
3501 		BMessage* currentMessage = Window()->CurrentMessage();
3502 		if (currentMessage == NULL
3503 			|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
3504 			modifiers = 0;
3505 		}
3506 	}
3507 
3508 	bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
3509 	bool optionKeyDown  = (modifiers & B_OPTION_KEY)  != 0;
3510 	bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
3511 
3512 	if ((commandKeyDown || optionKeyDown) && !controlKeyDown) {
3513 		fSelStart = fCaretOffset;
3514 		fSelEnd = _NextWordEnd(fCaretOffset) + 1;
3515 	}
3516 
3517 	if (fUndo) {
3518 		TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo);
3519 		if (!undoBuffer) {
3520 			delete fUndo;
3521 			fUndo = undoBuffer = new TypingUndoBuffer(this);
3522 		}
3523 		undoBuffer->ForwardErase();
3524 	}
3525 
3526 	// we may draw twice, so turn updates off for now
3527 	if (Window() != NULL)
3528 		Window()->DisableUpdates();
3529 
3530 	if (fSelStart == fSelEnd) {
3531 		if (fSelEnd != fText->Length())
3532 			fSelEnd = _NextInitialByte(fSelEnd);
3533 	} else
3534 		Highlight(fSelStart, fSelEnd);
3535 
3536 	DeleteText(fSelStart, fSelEnd);
3537 	fCaretOffset = fSelEnd = fSelStart;
3538 
3539 	_Refresh(fSelStart, fSelEnd, fCaretOffset);
3540 
3541 	// turn updates back on
3542 	if (Window() != NULL)
3543 		Window()->EnableUpdates();
3544 }
3545 
3546 
3547 //!	Handles when the Page Up, Page Down, Home, or End key is pressed.
3548 void
3549 BTextView::_HandlePageKey(uint32 pageKey, int32 modifiers)
3550 {
3551 	if (modifiers < 0) {
3552 		BMessage* currentMessage = Window()->CurrentMessage();
3553 		if (currentMessage == NULL
3554 			|| currentMessage->FindInt32("modifiers", &modifiers) != B_OK) {
3555 			modifiers = 0;
3556 		}
3557 	}
3558 
3559 	bool shiftKeyDown   = (modifiers & B_SHIFT_KEY)   != 0;
3560 	bool controlKeyDown = (modifiers & B_CONTROL_KEY) != 0;
3561 	bool optionKeyDown  = (modifiers & B_OPTION_KEY)  != 0;
3562 	bool commandKeyDown = (modifiers & B_COMMAND_KEY) != 0;
3563 
3564 	STELine* line = NULL;
3565 	int32 selStart = fSelStart;
3566 	int32 selEnd = fSelEnd;
3567 
3568 	int32 lastClickOffset = fCaretOffset;
3569 	switch (pageKey) {
3570 		case B_HOME:
3571 			if (!fEditable && !fSelectable) {
3572 				fCaretOffset = 0;
3573 				_ScrollTo(0, 0);
3574 				break;
3575 			} else {
3576 				if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
3577 					_ScrollTo(0, 0);
3578 					fCaretOffset = 0;
3579 				} else {
3580 					// get the start of the last line if caret is on it
3581 					line = (*fLines)[_LineAt(lastClickOffset)];
3582 					fCaretOffset = line->offset;
3583 				}
3584 
3585 				if (!shiftKeyDown)
3586 					selStart = selEnd = fCaretOffset;
3587 				else if (fCaretOffset != lastClickOffset) {
3588 					if (fCaretOffset < fSelStart) {
3589 						// extend selection to the left
3590 						selStart = fCaretOffset;
3591 						if (lastClickOffset > fSelStart) {
3592 							// caret has jumped across "anchor"
3593 							selEnd = fSelStart;
3594 						}
3595 					} else {
3596 						// shrink selection from the right
3597 						selEnd = fCaretOffset;
3598 					}
3599 				}
3600 			}
3601 			break;
3602 
3603 		case B_END:
3604 			if (!fEditable && !fSelectable) {
3605 				fCaretOffset = fText->Length();
3606 				_ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset);
3607 				break;
3608 			} else {
3609 				if (commandKeyDown && !optionKeyDown && !controlKeyDown) {
3610 					_ScrollTo(0, fTextRect.bottom + fLayoutData->bottomInset);
3611 					fCaretOffset = fText->Length();
3612 				} else {
3613 					// If we are on the last line, just go to the last
3614 					// character in the buffer, otherwise get the starting
3615 					// offset of the next line, and go to the previous character
3616 					int32 currentLine = _LineAt(lastClickOffset);
3617 					if (currentLine + 1 < fLines->NumLines()) {
3618 						line = (*fLines)[currentLine + 1];
3619 						fCaretOffset = _PreviousInitialByte(line->offset);
3620 					} else {
3621 						// This check is needed to avoid moving the cursor
3622 						// when the cursor is on the last line, and that line
3623 						// is empty
3624 						if (fCaretOffset != fText->Length()) {
3625 							fCaretOffset = fText->Length();
3626 							if (ByteAt(fCaretOffset - 1) == B_ENTER)
3627 								fCaretOffset--;
3628 						}
3629 					}
3630 				}
3631 
3632 				if (!shiftKeyDown)
3633 					selStart = selEnd = fCaretOffset;
3634 				else if (fCaretOffset != lastClickOffset) {
3635 					if (fCaretOffset > fSelEnd) {
3636 						// extend selection to the right
3637 						selEnd = fCaretOffset;
3638 						if (lastClickOffset < fSelEnd) {
3639 							// caret has jumped across "anchor"
3640 							selStart = fSelEnd;
3641 						}
3642 					} else {
3643 						// shrink selection from the left
3644 						selStart = fCaretOffset;
3645 					}
3646 				}
3647 			}
3648 			break;
3649 
3650 		case B_PAGE_UP:
3651 		{
3652 			float lineHeight;
3653 			BPoint currentPos = PointAt(fCaretOffset, &lineHeight);
3654 			BPoint nextPos(currentPos.x,
3655 				currentPos.y + lineHeight - Bounds().Height());
3656 			fCaretOffset = OffsetAt(nextPos);
3657 			nextPos = PointAt(fCaretOffset);
3658 			_ScrollBy(0, nextPos.y - currentPos.y);
3659 
3660 			if (!fEditable && !fSelectable)
3661 				break;
3662 
3663 			if (!shiftKeyDown)
3664 				selStart = selEnd = fCaretOffset;
3665 			else if (fCaretOffset != lastClickOffset) {
3666 				if (fCaretOffset < fSelStart) {
3667 					// extend selection to the top
3668 					selStart = fCaretOffset;
3669 					if (lastClickOffset > fSelStart) {
3670 						// caret has jumped across "anchor"
3671 						selEnd = fSelStart;
3672 					}
3673 				} else {
3674 					// shrink selection from the bottom
3675 					selEnd = fCaretOffset;
3676 				}
3677 			}
3678 
3679 			break;
3680 		}
3681 
3682 		case B_PAGE_DOWN:
3683 		{
3684 			BPoint currentPos = PointAt(fCaretOffset);
3685 			BPoint nextPos(currentPos.x, currentPos.y + Bounds().Height());
3686 			fCaretOffset = OffsetAt(nextPos);
3687 			nextPos = PointAt(fCaretOffset);
3688 			_ScrollBy(0, nextPos.y - currentPos.y);
3689 
3690 			if (!fEditable && !fSelectable)
3691 				break;
3692 
3693 			if (!shiftKeyDown)
3694 				selStart = selEnd = fCaretOffset;
3695 			else if (fCaretOffset != lastClickOffset) {
3696 				if (fCaretOffset > fSelEnd) {
3697 					// extend selection to the bottom
3698 					selEnd = fCaretOffset;
3699 					if (lastClickOffset < fSelEnd) {
3700 						// caret has jumped across "anchor"
3701 						selStart = fSelEnd;
3702 					}
3703 				} else {
3704 					// shrink selection from the top
3705 					selStart = fCaretOffset;
3706 				}
3707 			}
3708 
3709 			break;
3710 		}
3711 	}
3712 
3713 	if (fEditable || fSelectable) {
3714 		if (shiftKeyDown)
3715 			Select(selStart, selEnd);
3716 		else
3717 			Select(fCaretOffset, fCaretOffset);
3718 
3719 		ScrollToOffset(fCaretOffset);
3720 	}
3721 }
3722 
3723 
3724 /*!	Handles when an alpha-numeric key is pressed.
3725 
3726 	\param bytes The string or character associated with the key.
3727 	\param numBytes The amount of bytes containes in "bytes".
3728 */
3729 void
3730 BTextView::_HandleAlphaKey(const char* bytes, int32 numBytes)
3731 {
3732 	if (!fEditable)
3733 		return;
3734 
3735 	if (fUndo) {
3736 		TypingUndoBuffer* undoBuffer = dynamic_cast<TypingUndoBuffer*>(fUndo);
3737 		if (!undoBuffer) {
3738 			delete fUndo;
3739 			fUndo = undoBuffer = new TypingUndoBuffer(this);
3740 		}
3741 		undoBuffer->InputCharacter(numBytes);
3742 	}
3743 
3744 	if (fSelStart != fSelEnd) {
3745 		Highlight(fSelStart, fSelEnd);
3746 		DeleteText(fSelStart, fSelEnd);
3747 	}
3748 
3749 	// we may draw twice, so turn updates off for now
3750 	if (Window() != NULL)
3751 		Window()->DisableUpdates();
3752 
3753 	if (fAutoindent && numBytes == 1 && *bytes == B_ENTER) {
3754 		int32 start, offset;
3755 		start = offset = OffsetAt(_LineAt(fSelStart));
3756 
3757 		while (ByteAt(offset) != '\0' &&
3758 				(ByteAt(offset) == B_TAB || ByteAt(offset) == B_SPACE)
3759 				&& offset < fSelStart)
3760 			offset++;
3761 
3762 		_DoInsertText(bytes, numBytes, fSelStart, NULL);
3763 		if (start != offset)
3764 			_DoInsertText(Text() + start, offset - start, fSelStart, NULL);
3765 	} else
3766 		_DoInsertText(bytes, numBytes, fSelStart, NULL);
3767 
3768 	fCaretOffset = fSelEnd;
3769 	ScrollToOffset(fCaretOffset);
3770 
3771 	// turn updates back on
3772 	if (Window() != NULL)
3773 		Window()->EnableUpdates();
3774 }
3775 
3776 
3777 /*!	Redraw the text between the two given offsets, recalculating line-breaks
3778 	if needed.
3779 
3780 	\param fromOffset The offset from where to refresh.
3781 	\param toOffset The offset where to refresh to.
3782 	\param scrollTo Scroll the view to \a scrollTo offset if not \c INT32_MIN.
3783 */
3784 void
3785 BTextView::_Refresh(int32 fromOffset, int32 toOffset, int32 scrollTo)
3786 {
3787 	// TODO: Cleanup
3788 	float saveHeight = fTextRect.Height();
3789 	float saveWidth = fTextRect.Width();
3790 	int32 fromLine = _LineAt(fromOffset);
3791 	int32 toLine = _LineAt(toOffset);
3792 	int32 saveFromLine = fromLine;
3793 	int32 saveToLine = toLine;
3794 
3795 	_RecalculateLineBreaks(&fromLine, &toLine);
3796 
3797 	// TODO: Maybe there is still something we can do without a window...
3798 	if (!Window())
3799 		return;
3800 
3801 	BRect bounds = Bounds();
3802 	float newHeight = fTextRect.Height();
3803 
3804 	// if the line breaks have changed, force an erase
3805 	if (fromLine != saveFromLine || toLine != saveToLine
3806 			|| newHeight != saveHeight) {
3807 		fromOffset = -1;
3808 	}
3809 
3810 	if (newHeight != saveHeight) {
3811 		// the text area has changed
3812 		if (newHeight < saveHeight)
3813 			toLine = _LineAt(BPoint(0.0f, saveHeight + fTextRect.top));
3814 		else
3815 			toLine = _LineAt(BPoint(0.0f, newHeight + fTextRect.top));
3816 	}
3817 
3818 	// draw only those lines that are visible
3819 	int32 fromVisible = _LineAt(BPoint(0.0f, bounds.top));
3820 	int32 toVisible = _LineAt(BPoint(0.0f, bounds.bottom));
3821 	fromLine = std::max(fromVisible, fromLine);
3822 	toLine = std::min(toLine, toVisible);
3823 
3824 	_AutoResize(false);
3825 
3826 	_RequestDrawLines(fromLine, toLine);
3827 
3828 	// erase the area below the text
3829 	BRect eraseRect = bounds;
3830 	eraseRect.top = fTextRect.top + (*fLines)[fLines->NumLines()]->origin;
3831 	eraseRect.bottom = fTextRect.top + saveHeight;
3832 	if (eraseRect.bottom > eraseRect.top && eraseRect.Intersects(bounds)) {
3833 		SetLowColor(ViewColor());
3834 		FillRect(eraseRect, B_SOLID_LOW);
3835 	}
3836 
3837 	// update the scroll bars if the text area has changed
3838 	if (newHeight != saveHeight || fMinTextRectWidth != saveWidth)
3839 		_UpdateScrollbars();
3840 
3841 	if (scrollTo != INT32_MIN)
3842 		ScrollToOffset(scrollTo);
3843 
3844 	Flush();
3845 }
3846 
3847 
3848 /*!	Recalculate line breaks between two lines.
3849 
3850 	\param startLine The line number to start recalculating line breaks.
3851 	\param endLine The line number to stop recalculating line breaks.
3852 */
3853 void
3854 BTextView::_RecalculateLineBreaks(int32* startLine, int32* endLine)
3855 {
3856 	CALLED();
3857 
3858 	float width = fTextRect.Width();
3859 
3860 	// don't try to compute anything if the text rect is not set
3861 	if (!fTextRect.IsValid() || width == 0)
3862 		return;
3863 
3864 	// sanity check
3865 	*startLine = (*startLine < 0) ? 0 : *startLine;
3866 	*endLine = (*endLine > fLines->NumLines() - 1) ? fLines->NumLines() - 1
3867 		: *endLine;
3868 
3869 	int32 textLength = fText->Length();
3870 	int32 lineIndex = (*startLine > 0) ? *startLine - 1 : 0;
3871 	int32 recalThreshold = (*fLines)[*endLine + 1]->offset;
3872 	STELine* curLine = (*fLines)[lineIndex];
3873 	STELine* nextLine = curLine + 1;
3874 
3875 	do {
3876 		float ascent, descent;
3877 		int32 fromOffset = curLine->offset;
3878 		int32 toOffset = _FindLineBreak(fromOffset, &ascent, &descent, &width);
3879 
3880 		curLine->ascent = ascent;
3881 		curLine->width = width;
3882 
3883 		// we want to advance at least by one character
3884 		int32 nextOffset = _NextInitialByte(fromOffset);
3885 		if (toOffset < nextOffset && fromOffset < textLength)
3886 			toOffset = nextOffset;
3887 
3888 		lineIndex++;
3889 		STELine saveLine = *nextLine;
3890 		if (lineIndex > fLines->NumLines() || toOffset < nextLine->offset) {
3891 			// the new line comes before the old line start, add a line
3892 			STELine newLine;
3893 			newLine.offset = toOffset;
3894 			newLine.origin = ceilf(curLine->origin + ascent + descent) + 1;
3895 			newLine.ascent = 0;
3896 			fLines->InsertLine(&newLine, lineIndex);
3897 		} else {
3898 			// update the existing line
3899 			nextLine->offset = toOffset;
3900 			nextLine->origin = ceilf(curLine->origin + ascent + descent) + 1;
3901 
3902 			// remove any lines that start before the current line
3903 			while (lineIndex < fLines->NumLines()
3904 				&& toOffset >= ((*fLines)[lineIndex] + 1)->offset) {
3905 				fLines->RemoveLines(lineIndex + 1);
3906 			}
3907 
3908 			nextLine = (*fLines)[lineIndex];
3909 			if (nextLine->offset == saveLine.offset) {
3910 				if (nextLine->offset >= recalThreshold) {
3911 					if (nextLine->origin != saveLine.origin)
3912 						fLines->BumpOrigin(nextLine->origin - saveLine.origin,
3913 							lineIndex + 1);
3914 					break;
3915 				}
3916 			} else {
3917 				if (lineIndex > 0 && lineIndex == *startLine)
3918 					*startLine = lineIndex - 1;
3919 			}
3920 		}
3921 
3922 		curLine = (*fLines)[lineIndex];
3923 		nextLine = curLine + 1;
3924 	} while (curLine->offset < textLength);
3925 
3926 	// make sure that the sentinel line (which starts at the end of the buffer)
3927 	// has always a width of 0
3928 	(*fLines)[fLines->NumLines()]->width = 0;
3929 
3930 	// update text rect
3931 	fTextRect.left = Bounds().left + fLayoutData->leftInset;
3932 	fTextRect.right = Bounds().right - fLayoutData->rightInset;
3933 
3934 	// always set text rect bottom
3935 	float newHeight = TextHeight(0, fLines->NumLines() - 1);
3936 	fTextRect.bottom = fTextRect.top + newHeight;
3937 
3938 	if (!fWrap) {
3939 		fMinTextRectWidth = fLines->MaxWidth();
3940 
3941 		// expand width if needed
3942 		switch (fAlignment) {
3943 			default:
3944 			case B_ALIGN_LEFT:
3945 				// move right edge
3946 				fTextRect.right = fTextRect.left + fMinTextRectWidth;
3947 				break;
3948 
3949 			case B_ALIGN_RIGHT:
3950 				// move left edge
3951 				fTextRect.left = fTextRect.right - fMinTextRectWidth;
3952 				break;
3953 
3954 			case B_ALIGN_CENTER:
3955 				// move both edges
3956 				fTextRect.InsetBy(roundf((fTextRect.Width()
3957 					- fMinTextRectWidth) / 2), 0);
3958 				break;
3959 		}
3960 
3961 		_ValidateTextRect();
3962 	}
3963 
3964 	*endLine = lineIndex - 1;
3965 	*startLine = std::min(*startLine, *endLine);
3966 }
3967 
3968 
3969 void
3970 BTextView::_ValidateTextRect()
3971 {
3972 	// text rect right must be greater than left
3973 	if (fTextRect.right <= fTextRect.left)
3974 		fTextRect.right = fTextRect.left + 1;
3975 	// text rect bottom must be greater than top
3976 	if (fTextRect.bottom <= fTextRect.top)
3977 		fTextRect.bottom = fTextRect.top + 1;
3978 }
3979 
3980 
3981 int32
3982 BTextView::_FindLineBreak(int32 fromOffset, float* _ascent, float* _descent,
3983 	float* inOutWidth)
3984 {
3985 	*_ascent = 0.0;
3986 	*_descent = 0.0;
3987 
3988 	const int32 limit = fText->Length();
3989 
3990 	// is fromOffset at the end?
3991 	if (fromOffset >= limit) {
3992 		// try to return valid height info anyway
3993 		if (fStyles->NumRuns() > 0) {
3994 			fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, _ascent,
3995 				_descent);
3996 		} else {
3997 			if (fStyles->IsValidNullStyle()) {
3998 				const BFont* font = NULL;
3999 				fStyles->GetNullStyle(&font, NULL);
4000 
4001 				font_height fh;
4002 				font->GetHeight(&fh);
4003 				*_ascent = fh.ascent;
4004 				*_descent = fh.descent + fh.leading;
4005 			}
4006 		}
4007 		*inOutWidth = 0;
4008 
4009 		return limit;
4010 	}
4011 
4012 	int32 offset = fromOffset;
4013 
4014 	if (!fWrap) {
4015 		// Text wrapping is turned off.
4016 		// Just find the offset of the first \n character
4017 		offset = limit - fromOffset;
4018 		fText->FindChar(B_ENTER, fromOffset, &offset);
4019 		offset += fromOffset;
4020 		int32 toOffset = (offset < limit) ? offset : limit;
4021 
4022 		*inOutWidth = _TabExpandedStyledWidth(fromOffset, toOffset - fromOffset,
4023 			_ascent, _descent);
4024 
4025 		return offset < limit ? offset + 1 : limit;
4026 	}
4027 
4028 	bool done = false;
4029 	float ascent = 0.0;
4030 	float descent = 0.0;
4031 	int32 delta = 0;
4032 	float deltaWidth = 0.0;
4033 	float strWidth = 0.0;
4034 	uchar theChar;
4035 
4036 	// wrap the text
4037 	while (offset < limit && !done) {
4038 		// find the next line break candidate
4039 		for (; (offset + delta) < limit; delta++) {
4040 			if (CanEndLine(offset + delta)) {
4041 				theChar = fText->RealCharAt(offset + delta);
4042 				if (theChar != B_SPACE && theChar != B_TAB
4043 					&& theChar != B_ENTER) {
4044 					// we are scanning for trailing whitespace below, so we
4045 					// have to skip non-whitespace characters, that can end
4046 					// the line, here
4047 					delta++;
4048 				}
4049 				break;
4050 			}
4051 		}
4052 
4053 		int32 deltaBeforeWhitespace = delta;
4054 		// now skip over trailing whitespace, if any
4055 		for (; (offset + delta) < limit; delta++) {
4056 			theChar = fText->RealCharAt(offset + delta);
4057 			if (theChar == B_ENTER) {
4058 				// found a newline, we're done!
4059 				done = true;
4060 				delta++;
4061 				break;
4062 			} else if (theChar != B_SPACE && theChar != B_TAB) {
4063 				// stop at anything else than trailing whitespace
4064 				break;
4065 			}
4066 		}
4067 
4068 		delta = std::max(delta, (int32)1);
4069 
4070 		// do not include B_ENTER-terminator into width & height calculations
4071 		deltaWidth = _TabExpandedStyledWidth(offset,
4072 								done ? delta - 1 : delta, &ascent, &descent);
4073 		strWidth += deltaWidth;
4074 
4075 		if (strWidth >= *inOutWidth) {
4076 			// we've found where the line will wrap
4077 			done = true;
4078 
4079 			// we have included trailing whitespace in the width computation
4080 			// above, but that is not being shown anyway, so we try again
4081 			// without the trailing whitespace
4082 			if (delta == deltaBeforeWhitespace) {
4083 				// there is no trailing whitespace, no point in trying
4084 				break;
4085 			}
4086 
4087 			// reset string width to start of current run ...
4088 			strWidth -= deltaWidth;
4089 
4090 			// ... and compute the resulting width (of visible characters)
4091 			strWidth += _StyledWidth(offset, deltaBeforeWhitespace, NULL, NULL);
4092 			if (strWidth >= *inOutWidth) {
4093 				// width of visible characters exceeds line, we need to wrap
4094 				// before the current "word"
4095 				break;
4096 			}
4097 		}
4098 
4099 		*_ascent = std::max(ascent, *_ascent);
4100 		*_descent = std::max(descent, *_descent);
4101 
4102 		offset += delta;
4103 		delta = 0;
4104 	}
4105 
4106 	if (offset - fromOffset < 1) {
4107 		// there weren't any words that fit entirely in this line
4108 		// force a break in the middle of a word
4109 		*_ascent = 0.0;
4110 		*_descent = 0.0;
4111 		strWidth = 0.0;
4112 
4113 		int32 current = fromOffset;
4114 		for (offset = _NextInitialByte(current); current < limit;
4115 				current = offset, offset = _NextInitialByte(offset)) {
4116 			strWidth += _StyledWidth(current, offset - current, &ascent,
4117 				&descent);
4118 			if (strWidth >= *inOutWidth) {
4119 				offset = _PreviousInitialByte(offset);
4120 				break;
4121 			}
4122 
4123 			*_ascent = std::max(ascent, *_ascent);
4124 			*_descent = std::max(descent, *_descent);
4125 		}
4126 	}
4127 
4128 	return std::min(offset, limit);
4129 }
4130 
4131 
4132 int32
4133 BTextView::_PreviousLineStart(int32 offset)
4134 {
4135 	if (offset <= 0)
4136 		return 0;
4137 
4138 	while (offset > 0) {
4139 		offset = _PreviousInitialByte(offset);
4140 		if (_CharClassification(offset) == CHAR_CLASS_WHITESPACE
4141 			&& ByteAt(offset) == B_ENTER) {
4142 			return offset + 1;
4143 		}
4144 	}
4145 
4146 	return offset;
4147 }
4148 
4149 
4150 int32
4151 BTextView::_NextLineEnd(int32 offset)
4152 {
4153 	int32 textLen = fText->Length();
4154 	if (offset >= textLen)
4155 		return textLen;
4156 
4157 	while (offset < textLen) {
4158 		if (_CharClassification(offset) == CHAR_CLASS_WHITESPACE
4159 			&& ByteAt(offset) == B_ENTER) {
4160 			break;
4161 		}
4162 		offset = _NextInitialByte(offset);
4163 	}
4164 
4165 	return offset;
4166 }
4167 
4168 
4169 int32
4170 BTextView::_PreviousWordBoundary(int32 offset)
4171 {
4172 	uint32 charType = _CharClassification(offset);
4173 	int32 previous;
4174 	while (offset > 0) {
4175 		previous = _PreviousInitialByte(offset);
4176 		if (_CharClassification(previous) != charType)
4177 			break;
4178 		offset = previous;
4179 	}
4180 
4181 	return offset;
4182 }
4183 
4184 
4185 int32
4186 BTextView::_NextWordBoundary(int32 offset)
4187 {
4188 	int32 textLen = fText->Length();
4189 	uint32 charType = _CharClassification(offset);
4190 	while (offset < textLen) {
4191 		offset = _NextInitialByte(offset);
4192 		if (_CharClassification(offset) != charType)
4193 			break;
4194 	}
4195 
4196 	return offset;
4197 }
4198 
4199 
4200 int32
4201 BTextView::_PreviousWordStart(int32 offset)
4202 {
4203 	if (offset <= 1)
4204 		return 0;
4205 
4206 	--offset;
4207 		// need to look at previous char
4208 	if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) {
4209 		// skip non-word characters
4210 		while (offset > 0) {
4211 			offset = _PreviousInitialByte(offset);
4212 			if (_CharClassification(offset) == CHAR_CLASS_DEFAULT)
4213 				break;
4214 		}
4215 	}
4216 	while (offset > 0) {
4217 		// skip to start of word
4218 		int32 previous = _PreviousInitialByte(offset);
4219 		if (_CharClassification(previous) != CHAR_CLASS_DEFAULT)
4220 			break;
4221 		offset = previous;
4222 	}
4223 
4224 	return offset;
4225 }
4226 
4227 
4228 int32
4229 BTextView::_NextWordEnd(int32 offset)
4230 {
4231 	int32 textLen = fText->Length();
4232 	if (_CharClassification(offset) != CHAR_CLASS_DEFAULT) {
4233 		// skip non-word characters
4234 		while (offset < textLen) {
4235 			offset = _NextInitialByte(offset);
4236 			if (_CharClassification(offset) == CHAR_CLASS_DEFAULT)
4237 				break;
4238 		}
4239 	}
4240 	while (offset < textLen) {
4241 		// skip to end of word
4242 		offset = _NextInitialByte(offset);
4243 		if (_CharClassification(offset) != CHAR_CLASS_DEFAULT)
4244 			break;
4245 	}
4246 
4247 	return offset;
4248 }
4249 
4250 
4251 /*!	Returns the width used by the characters starting at the given
4252 	offset with the given length, expanding all tab characters as needed.
4253 */
4254 float
4255 BTextView::_TabExpandedStyledWidth(int32 offset, int32 length, float* _ascent,
4256 	float* _descent) const
4257 {
4258 	float ascent = 0.0;
4259 	float descent = 0.0;
4260 	float maxAscent = 0.0;
4261 	float maxDescent = 0.0;
4262 
4263 	float width = 0.0;
4264 	int32 numBytes = length;
4265 	bool foundTab = false;
4266 	do {
4267 		foundTab = fText->FindChar(B_TAB, offset, &numBytes);
4268 		width += _StyledWidth(offset, numBytes, &ascent, &descent);
4269 
4270 		if (maxAscent < ascent)
4271 			maxAscent = ascent;
4272 		if (maxDescent < descent)
4273 			maxDescent = descent;
4274 
4275 		if (foundTab) {
4276 			width += _ActualTabWidth(width);
4277 			numBytes++;
4278 		}
4279 
4280 		offset += numBytes;
4281 		length -= numBytes;
4282 		numBytes = length;
4283 	} while (foundTab && length > 0);
4284 
4285 	if (_ascent != NULL)
4286 		*_ascent = maxAscent;
4287 	if (_descent != NULL)
4288 		*_descent = maxDescent;
4289 
4290 	return width;
4291 }
4292 
4293 
4294 /*!	Calculate the width of the text within the given limits.
4295 
4296 	\param fromOffset The offset where to start.
4297 	\param length The length of the text to examine.
4298 	\param _ascent A pointer to a float which will contain the maximum ascent.
4299 	\param _descent A pointer to a float which will contain the maximum descent.
4300 
4301 	\return The width for the text within the given limits.
4302 */
4303 float
4304 BTextView::_StyledWidth(int32 fromOffset, int32 length, float* _ascent,
4305 	float* _descent) const
4306 {
4307 	if (length == 0) {
4308 		// determine height of char at given offset, but return empty width
4309 		fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, _ascent,
4310 			_descent);
4311 		return 0.0;
4312 	}
4313 
4314 	float result = 0.0;
4315 	float ascent = 0.0;
4316 	float descent = 0.0;
4317 	float maxAscent = 0.0;
4318 	float maxDescent = 0.0;
4319 
4320 	// iterate through the style runs
4321 	const BFont* font = NULL;
4322 	int32 numBytes;
4323 	while ((numBytes = fStyles->Iterate(fromOffset, length, fInline, &font,
4324 			NULL, &ascent, &descent)) != 0) {
4325 		maxAscent = std::max(ascent, maxAscent);
4326 		maxDescent = std::max(descent, maxDescent);
4327 
4328 #if USE_WIDTHBUFFER
4329 		// Use _BWidthBuffer_ if possible
4330 		if (BPrivate::gWidthBuffer != NULL) {
4331 			result += BPrivate::gWidthBuffer->StringWidth(*fText, fromOffset,
4332 				numBytes, font);
4333 		} else {
4334 #endif
4335 			const char* text = fText->GetString(fromOffset, &numBytes);
4336 			result += font->StringWidth(text, numBytes);
4337 
4338 #if USE_WIDTHBUFFER
4339 		}
4340 #endif
4341 
4342 		fromOffset += numBytes;
4343 		length -= numBytes;
4344 	}
4345 
4346 	if (_ascent != NULL)
4347 		*_ascent = maxAscent;
4348 	if (_descent != NULL)
4349 		*_descent = maxDescent;
4350 
4351 	return result;
4352 }
4353 
4354 
4355 //!	Calculate the actual tab width for the given location.
4356 float
4357 BTextView::_ActualTabWidth(float location) const
4358 {
4359 	float tabWidth = fTabWidth - fmod(location, fTabWidth);
4360 	if (round(tabWidth) == 0)
4361 		tabWidth = fTabWidth;
4362 
4363 	return tabWidth;
4364 }
4365 
4366 
4367 void
4368 BTextView::_DoInsertText(const char* text, int32 length, int32 offset,
4369 	const text_run_array* runs)
4370 {
4371 	_CancelInputMethod();
4372 
4373 	if (fText->Length() + length > MaxBytes())
4374 		return;
4375 
4376 	if (fSelStart != fSelEnd)
4377 		Select(fSelStart, fSelStart);
4378 
4379 	const int32 textLength = fText->Length();
4380 	if (offset > textLength)
4381 		offset = textLength;
4382 
4383 	// copy data into buffer
4384 	InsertText(text, length, offset, runs);
4385 
4386 	// recalc line breaks and draw the text
4387 	_Refresh(offset, offset + length);
4388 }
4389 
4390 
4391 void
4392 BTextView::_DoDeleteText(int32 fromOffset, int32 toOffset)
4393 {
4394 	CALLED();
4395 }
4396 
4397 
4398 void
4399 BTextView::_DrawLine(BView* view, const int32 &lineNum,
4400 	const int32 &startOffset, const bool &erase, BRect &eraseRect,
4401 	BRegion &inputRegion)
4402 {
4403 	STELine* line = (*fLines)[lineNum];
4404 	float startLeft = fTextRect.left;
4405 	if (startOffset != -1) {
4406 		if (ByteAt(startOffset) == B_ENTER) {
4407 			// StartOffset is a newline
4408 			startLeft = PointAt(line->offset).x;
4409 		} else
4410 			startLeft = PointAt(startOffset).x;
4411 	} else if (fAlignment != B_ALIGN_LEFT) {
4412 		float alignmentOffset = fTextRect.Width() - LineWidth(lineNum);
4413 		if (fAlignment == B_ALIGN_CENTER)
4414 			alignmentOffset = floorf(alignmentOffset / 2);
4415 		startLeft += alignmentOffset;
4416 	}
4417 
4418 	int32 length = (line + 1)->offset;
4419 	if (startOffset != -1)
4420 		length -= startOffset;
4421 	else
4422 		length -= line->offset;
4423 
4424 	// DrawString() chokes if you draw a newline
4425 	if (ByteAt((line + 1)->offset - 1) == B_ENTER)
4426 		length--;
4427 
4428 	view->MovePenTo(startLeft,
4429 		line->origin + line->ascent + fTextRect.top + 1);
4430 
4431 	if (erase) {
4432 		eraseRect.top = line->origin + fTextRect.top;
4433 		eraseRect.bottom = (line + 1)->origin + fTextRect.top;
4434 		view->FillRect(eraseRect, B_SOLID_LOW);
4435 	}
4436 
4437 	// do we have any text to draw?
4438 	if (length <= 0)
4439 		return;
4440 
4441 	bool foundTab = false;
4442 	int32 tabChars = 0;
4443 	int32 numTabs = 0;
4444 	int32 offset = startOffset != -1 ? startOffset : line->offset;
4445 	const BFont* font = NULL;
4446 	const rgb_color* color = NULL;
4447 	int32 numBytes;
4448 	drawing_mode defaultTextRenderingMode = DrawingMode();
4449 	// iterate through each style on this line
4450 	while ((numBytes = fStyles->Iterate(offset, length, fInline, &font,
4451 			&color)) != 0) {
4452 		view->SetFont(font);
4453 		view->SetHighColor(*color);
4454 
4455 		tabChars = std::min(numBytes, length);
4456 		do {
4457 			foundTab = fText->FindChar(B_TAB, offset, &tabChars);
4458 			if (foundTab) {
4459 				do {
4460 					numTabs++;
4461 					if (ByteAt(offset + tabChars + numTabs) != B_TAB)
4462 						break;
4463 				} while ((tabChars + numTabs) < numBytes);
4464 			}
4465 
4466 			drawing_mode textRenderingMode = defaultTextRenderingMode;
4467 
4468 			if (inputRegion.CountRects() > 0
4469 				&& ((offset <= fInline->Offset()
4470 					&& fInline->Offset() < offset + tabChars)
4471 				|| (fInline->Offset() <= offset
4472 					&& offset < fInline->Offset() + fInline->Length()))) {
4473 
4474 				textRenderingMode = B_OP_OVER;
4475 
4476 				BRegion textRegion;
4477 				GetTextRegion(offset, offset + length, &textRegion);
4478 
4479 				textRegion.IntersectWith(&inputRegion);
4480 				view->PushState();
4481 
4482 				// Highlight in blue the inputted text
4483 				view->SetHighColor(kBlueInputColor);
4484 				view->FillRect(textRegion.Frame());
4485 
4486 				// Highlight in red the selected part
4487 				if (fInline->SelectionLength() > 0) {
4488 					BRegion selectedRegion;
4489 					GetTextRegion(fInline->Offset()
4490 						+ fInline->SelectionOffset(), fInline->Offset()
4491 						+ fInline->SelectionOffset()
4492 						+ fInline->SelectionLength(), &selectedRegion);
4493 
4494 					textRegion.IntersectWith(&selectedRegion);
4495 
4496 					view->SetHighColor(kRedInputColor);
4497 					view->FillRect(textRegion.Frame());
4498 				}
4499 
4500 				view->PopState();
4501 			}
4502 
4503 			int32 size = tabChars;
4504 			const char* stringToDraw = fText->GetString(offset, &size);
4505 			view->SetDrawingMode(textRenderingMode);
4506 			view->DrawString(stringToDraw, size);
4507 
4508 			if (foundTab) {
4509 				float penPos = PenLocation().x - fTextRect.left;
4510 				switch (fAlignment) {
4511 					default:
4512 					case B_ALIGN_LEFT:
4513 						// nothing more to do
4514 						break;
4515 
4516 					case B_ALIGN_RIGHT:
4517 						// subtract distance from left to line
4518 						penPos -= fTextRect.Width() - LineWidth(lineNum);
4519 						break;
4520 
4521 					case B_ALIGN_CENTER:
4522 						// subtract half distance from left to line
4523 						penPos -= floorf((fTextRect.Width()
4524 							- LineWidth(lineNum)) / 2);
4525 						break;
4526 				}
4527 				float tabWidth = _ActualTabWidth(penPos);
4528 
4529 				// add in the rest of the tabs (if there are any)
4530 				tabWidth += ((numTabs - 1) * fTabWidth);
4531 
4532 				// move pen by tab(s) width
4533 				view->MovePenBy(tabWidth, 0.0);
4534 				tabChars += numTabs;
4535 			}
4536 
4537 			offset += tabChars;
4538 			length -= tabChars;
4539 			numBytes -= tabChars;
4540 			tabChars = std::min(numBytes, length);
4541 			numTabs = 0;
4542 		} while (foundTab && tabChars > 0);
4543 	}
4544 }
4545 
4546 
4547 void
4548 BTextView::_DrawLines(int32 startLine, int32 endLine, int32 startOffset,
4549 	bool erase)
4550 {
4551 	if (!Window())
4552 		return;
4553 
4554 	const BRect bounds(Bounds());
4555 
4556 	// clip the text extending to end of selection
4557 	BRect clipRect(fTextRect);
4558 	clipRect.left = std::min(fTextRect.left,
4559 		bounds.left + fLayoutData->leftInset);
4560 	clipRect.right = std::max(fTextRect.right,
4561 		bounds.right - fLayoutData->rightInset);
4562 	clipRect = bounds & clipRect;
4563 
4564 	BRegion newClip;
4565 	newClip.Set(clipRect);
4566 	ConstrainClippingRegion(&newClip);
4567 
4568 	// set the low color to the view color so that
4569 	// drawing to a non-white background will work
4570 	SetLowColor(ViewColor());
4571 
4572 	BView* view = NULL;
4573 	if (fOffscreen == NULL)
4574 		view = this;
4575 	else {
4576 		fOffscreen->Lock();
4577 		view = fOffscreen->ChildAt(0);
4578 		view->SetLowColor(ViewColor());
4579 		view->FillRect(view->Bounds(), B_SOLID_LOW);
4580 	}
4581 
4582 	long maxLine = fLines->NumLines() - 1;
4583 	if (startLine < 0)
4584 		startLine = 0;
4585 	if (endLine > maxLine)
4586 		endLine = maxLine;
4587 
4588 	// TODO: See if we can avoid this
4589 	if (fAlignment != B_ALIGN_LEFT)
4590 		erase = true;
4591 
4592 	BRect eraseRect = clipRect;
4593 	int32 startEraseLine = startLine;
4594 	STELine* line = (*fLines)[startLine];
4595 
4596 	if (erase && startOffset != -1 && fAlignment == B_ALIGN_LEFT) {
4597 		// erase only to the right of startOffset
4598 		startEraseLine++;
4599 		int32 startErase = startOffset;
4600 
4601 		BPoint erasePoint = PointAt(startErase);
4602 		eraseRect.left = erasePoint.x;
4603 		eraseRect.top = erasePoint.y;
4604 		eraseRect.bottom = (line + 1)->origin + fTextRect.top;
4605 
4606 		view->FillRect(eraseRect, B_SOLID_LOW);
4607 
4608 		eraseRect = clipRect;
4609 	}
4610 
4611 	BRegion inputRegion;
4612 	if (fInline != NULL && fInline->IsActive()) {
4613 		GetTextRegion(fInline->Offset(), fInline->Offset() + fInline->Length(),
4614 			&inputRegion);
4615 	}
4616 
4617 	//BPoint leftTop(startLeft, line->origin);
4618 	for (int32 lineNum = startLine; lineNum <= endLine; lineNum++) {
4619 		const bool eraseThisLine = erase && lineNum >= startEraseLine;
4620 		_DrawLine(view, lineNum, startOffset, eraseThisLine, eraseRect,
4621 			inputRegion);
4622 		startOffset = -1;
4623 			// Set this to -1 so the next iteration will use the line offset
4624 	}
4625 
4626 	// draw the caret/hilite the selection
4627 	if (fActive) {
4628 		if (fSelStart != fSelEnd) {
4629 			if (fSelectable)
4630 				Highlight(fSelStart, fSelEnd);
4631 		} else {
4632 			if (fCaretVisible)
4633 				_DrawCaret(fSelStart, true);
4634 		}
4635 	}
4636 
4637 	if (fOffscreen != NULL) {
4638 		view->Sync();
4639 		/*BPoint penLocation = view->PenLocation();
4640 		BRect drawRect(leftTop.x, leftTop.y, penLocation.x, penLocation.y);
4641 		DrawBitmap(fOffscreen, drawRect, drawRect);*/
4642 		fOffscreen->Unlock();
4643 	}
4644 
4645 	ConstrainClippingRegion(NULL);
4646 }
4647 
4648 
4649 void
4650 BTextView::_RequestDrawLines(int32 startLine, int32 endLine)
4651 {
4652 	if (!Window())
4653 		return;
4654 
4655 	long maxLine = fLines->NumLines() - 1;
4656 
4657 	STELine* from = (*fLines)[startLine];
4658 	STELine* to = endLine == maxLine ? NULL : (*fLines)[endLine + 1];
4659 	BRect invalidRect(Bounds().left, from->origin + fTextRect.top,
4660 		Bounds().right,
4661 		to != NULL ? to->origin + fTextRect.top : fTextRect.bottom);
4662 	Invalidate(invalidRect);
4663 }
4664 
4665 
4666 void
4667 BTextView::_DrawCaret(int32 offset, bool visible)
4668 {
4669 	float lineHeight;
4670 	BPoint caretPoint = PointAt(offset, &lineHeight);
4671 	caretPoint.x = std::min(caretPoint.x, fTextRect.right);
4672 
4673 	BRect caretRect;
4674 	caretRect.left = caretRect.right = caretPoint.x;
4675 	caretRect.top = caretPoint.y;
4676 	caretRect.bottom = caretPoint.y + lineHeight - 1;
4677 
4678 	if (visible)
4679 		InvertRect(caretRect);
4680 	else
4681 		Invalidate(caretRect);
4682 }
4683 
4684 
4685 inline void
4686 BTextView::_ShowCaret()
4687 {
4688 	if (fActive && !fCaretVisible && fEditable && fSelStart == fSelEnd)
4689 		_InvertCaret();
4690 }
4691 
4692 
4693 inline void
4694 BTextView::_HideCaret()
4695 {
4696 	if (fCaretVisible && fSelStart == fSelEnd)
4697 		_InvertCaret();
4698 }
4699 
4700 
4701 //!	Hides the caret if it is being shown, and if it's hidden, shows it.
4702 void
4703 BTextView::_InvertCaret()
4704 {
4705 	fCaretVisible = !fCaretVisible;
4706 	_DrawCaret(fSelStart, fCaretVisible);
4707 	fCaretTime = system_time();
4708 }
4709 
4710 
4711 /*!	Place the dragging caret at the given offset.
4712 
4713 	\param offset The offset (zero based within the object's text) where to
4714 	       place the dragging caret. If it's -1, hide the caret.
4715 */
4716 void
4717 BTextView::_DragCaret(int32 offset)
4718 {
4719 	// does the caret need to move?
4720 	if (offset == fDragOffset)
4721 		return;
4722 
4723 	// hide the previous drag caret
4724 	if (fDragOffset != -1)
4725 		_DrawCaret(fDragOffset, false);
4726 
4727 	// do we have a new location?
4728 	if (offset != -1) {
4729 		if (fActive) {
4730 			// ignore if offset is within active selection
4731 			if (offset >= fSelStart && offset <= fSelEnd) {
4732 				fDragOffset = -1;
4733 				return;
4734 			}
4735 		}
4736 
4737 		_DrawCaret(offset, true);
4738 	}
4739 
4740 	fDragOffset = offset;
4741 }
4742 
4743 
4744 void
4745 BTextView::_StopMouseTracking()
4746 {
4747 	delete fTrackingMouse;
4748 	fTrackingMouse = NULL;
4749 }
4750 
4751 
4752 bool
4753 BTextView::_PerformMouseUp(BPoint where)
4754 {
4755 	if (fTrackingMouse == NULL)
4756 		return false;
4757 
4758 	if (fTrackingMouse->selectionRect.IsValid())
4759 		Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset);
4760 
4761 	_StopMouseTracking();
4762 	// adjust cursor if necessary
4763 	_TrackMouse(where, NULL, true);
4764 
4765 	return true;
4766 }
4767 
4768 
4769 bool
4770 BTextView::_PerformMouseMoved(BPoint where, uint32 code)
4771 {
4772 	fWhere = where;
4773 
4774 	if (fTrackingMouse == NULL)
4775 		return false;
4776 
4777 	int32 currentOffset = OffsetAt(where);
4778 	if (fTrackingMouse->selectionRect.IsValid()) {
4779 		// we are tracking the mouse for drag action, if the mouse has moved
4780 		// to another index or more than three pixels from where it was clicked,
4781 		// we initiate a drag now:
4782 		if (currentOffset != fTrackingMouse->clickOffset
4783 			|| fabs(fTrackingMouse->where.x - where.x) > 3
4784 			|| fabs(fTrackingMouse->where.y - where.y) > 3) {
4785 			_StopMouseTracking();
4786 			_InitiateDrag();
4787 			return true;
4788 		}
4789 		return false;
4790 	}
4791 
4792 	switch (fClickCount) {
4793 		case 3:
4794 			// triple click, extend selection linewise
4795 			if (currentOffset <= fTrackingMouse->anchor) {
4796 				fTrackingMouse->selStart
4797 					= (*fLines)[_LineAt(currentOffset)]->offset;
4798 				fTrackingMouse->selEnd = fTrackingMouse->shiftDown
4799 					? fSelEnd
4800 					: (*fLines)[_LineAt(fTrackingMouse->anchor) + 1]->offset;
4801 			} else {
4802 				fTrackingMouse->selStart
4803 					= fTrackingMouse->shiftDown
4804 						? fSelStart
4805 						: (*fLines)[_LineAt(fTrackingMouse->anchor)]->offset;
4806 				fTrackingMouse->selEnd
4807 					= (*fLines)[_LineAt(currentOffset) + 1]->offset;
4808 			}
4809 			break;
4810 
4811 		case 2:
4812 			// double click, extend selection wordwise
4813 			if (currentOffset <= fTrackingMouse->anchor) {
4814 				fTrackingMouse->selStart = _PreviousWordBoundary(currentOffset);
4815 				fTrackingMouse->selEnd
4816 					= fTrackingMouse->shiftDown
4817 						? fSelEnd
4818 						: _NextWordBoundary(fTrackingMouse->anchor);
4819 			} else {
4820 				fTrackingMouse->selStart
4821 					= fTrackingMouse->shiftDown
4822 						? fSelStart
4823 						: _PreviousWordBoundary(fTrackingMouse->anchor);
4824 				fTrackingMouse->selEnd = _NextWordBoundary(currentOffset);
4825 			}
4826 			break;
4827 
4828 		default:
4829 			// new click, extend selection char by char
4830 			if (currentOffset <= fTrackingMouse->anchor) {
4831 				fTrackingMouse->selStart = currentOffset;
4832 				fTrackingMouse->selEnd
4833 					= fTrackingMouse->shiftDown
4834 						? fSelEnd : fTrackingMouse->anchor;
4835 			} else {
4836 				fTrackingMouse->selStart
4837 					= fTrackingMouse->shiftDown
4838 						? fSelStart : fTrackingMouse->anchor;
4839 				fTrackingMouse->selEnd = currentOffset;
4840 			}
4841 			break;
4842 	}
4843 
4844 	// position caret to follow the direction of the selection
4845 	if (fTrackingMouse->selEnd != fSelEnd)
4846 		fCaretOffset = fTrackingMouse->selEnd;
4847 	else if (fTrackingMouse->selStart != fSelStart)
4848 		fCaretOffset = fTrackingMouse->selStart;
4849 
4850 	Select(fTrackingMouse->selStart, fTrackingMouse->selEnd);
4851 	_TrackMouse(where, NULL);
4852 
4853 	return true;
4854 }
4855 
4856 
4857 /*!	Tracks the mouse position, doing special actions like changing the
4858 	view cursor.
4859 
4860 	\param where The point where the mouse has moved.
4861 	\param message The dragging message, if there is any.
4862 	\param force Passed as second parameter of SetViewCursor()
4863 */
4864 void
4865 BTextView::_TrackMouse(BPoint where, const BMessage* message, bool force)
4866 {
4867 	BRegion textRegion;
4868 	GetTextRegion(fSelStart, fSelEnd, &textRegion);
4869 
4870 	if (message && AcceptsDrop(message))
4871 		_TrackDrag(where);
4872 	else if ((fSelectable || fEditable)
4873 		&& (fTrackingMouse != NULL || !textRegion.Contains(where))) {
4874 		SetViewCursor(B_CURSOR_I_BEAM, force);
4875 	} else
4876 		SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force);
4877 }
4878 
4879 
4880 //!	Tracks the mouse position when the user is dragging some data.
4881 void
4882 BTextView::_TrackDrag(BPoint where)
4883 {
4884 	CALLED();
4885 	if (Bounds().Contains(where))
4886 		_DragCaret(OffsetAt(where));
4887 }
4888 
4889 
4890 //!	Initiates a drag operation.
4891 void
4892 BTextView::_InitiateDrag()
4893 {
4894 	BMessage dragMessage(B_MIME_DATA);
4895 	BBitmap* dragBitmap = NULL;
4896 	BPoint bitmapPoint;
4897 	BHandler* dragHandler = NULL;
4898 
4899 	GetDragParameters(&dragMessage, &dragBitmap, &bitmapPoint, &dragHandler);
4900 	SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
4901 
4902 	if (dragBitmap != NULL)
4903 		DragMessage(&dragMessage, dragBitmap, bitmapPoint, dragHandler);
4904 	else {
4905 		BRegion region;
4906 		GetTextRegion(fSelStart, fSelEnd, &region);
4907 		BRect bounds = Bounds();
4908 		BRect dragRect = region.Frame();
4909 		if (!bounds.Contains(dragRect))
4910 			dragRect = bounds & dragRect;
4911 
4912 		DragMessage(&dragMessage, dragRect, dragHandler);
4913 	}
4914 
4915 	BMessenger messenger(this);
4916 	BMessage message(_DISPOSE_DRAG_);
4917 	fDragRunner = new (nothrow) BMessageRunner(messenger, &message, 100000);
4918 }
4919 
4920 
4921 //!	Handles when some data is dropped on the view.
4922 bool
4923 BTextView::_MessageDropped(BMessage* message, BPoint where, BPoint offset)
4924 {
4925 	ASSERT(message);
4926 
4927 	void* from = NULL;
4928 	bool internalDrop = false;
4929 	if (message->FindPointer("be:originator", &from) == B_OK
4930 			&& from == this && fSelEnd != fSelStart)
4931 		internalDrop = true;
4932 
4933 	_DragCaret(-1);
4934 
4935 	delete fDragRunner;
4936 	fDragRunner = NULL;
4937 
4938 	_TrackMouse(where, NULL);
4939 
4940 	// are we sure we like this message?
4941 	if (!AcceptsDrop(message))
4942 		return false;
4943 
4944 	int32 dropOffset = OffsetAt(where);
4945 	if (dropOffset > fText->Length())
4946 		dropOffset = fText->Length();
4947 
4948 	// if this view initiated the drag, move instead of copy
4949 	if (internalDrop) {
4950 		// dropping onto itself?
4951 		if (dropOffset >= fSelStart && dropOffset <= fSelEnd)
4952 			return true;
4953 	}
4954 
4955 	ssize_t dataLength = 0;
4956 	const char* text = NULL;
4957 	entry_ref ref;
4958 	if (message->FindData("text/plain", B_MIME_TYPE, (const void**)&text,
4959 			&dataLength) == B_OK) {
4960 		text_run_array* runArray = NULL;
4961 		ssize_t runLength = 0;
4962 		if (fStylable) {
4963 			message->FindData("application/x-vnd.Be-text_run_array",
4964 				B_MIME_TYPE, (const void**)&runArray, &runLength);
4965 		}
4966 
4967 		_FilterDisallowedChars((char*)text, dataLength, runArray);
4968 
4969 		if (dataLength < 1) {
4970 			beep();
4971 			return true;
4972 		}
4973 
4974 		if (fUndo) {
4975 			delete fUndo;
4976 			fUndo = new DropUndoBuffer(this, text, dataLength, runArray,
4977 				runLength, dropOffset, internalDrop);
4978 		}
4979 
4980 		if (internalDrop) {
4981 			if (dropOffset > fSelEnd)
4982 				dropOffset -= dataLength;
4983 			Delete();
4984 		}
4985 
4986 		Insert(dropOffset, text, dataLength, runArray);
4987 		if (IsFocus())
4988 			Select(dropOffset, dropOffset + dataLength);
4989 	}
4990 
4991 	return true;
4992 }
4993 
4994 
4995 void
4996 BTextView::_PerformAutoScrolling()
4997 {
4998 	// Scroll the view a bit if mouse is outside the view bounds
4999 	BRect bounds = Bounds();
5000 	BPoint scrollBy(B_ORIGIN);
5001 
5002 	// R5 does a pretty soft auto-scroll, we try to do the same by
5003 	// simply scrolling the distance between cursor and border
5004 	if (fWhere.x > bounds.right)
5005 		scrollBy.x = fWhere.x - bounds.right;
5006 	else if (fWhere.x < bounds.left)
5007 		scrollBy.x = fWhere.x - bounds.left; // negative value
5008 
5009 	// prevent from scrolling out of view
5010 	if (scrollBy.x != 0.0) {
5011 		float rightMax = fTextRect.right + fLayoutData->rightInset;
5012 		if (bounds.right + scrollBy.x > rightMax)
5013 			scrollBy.x = rightMax - bounds.right;
5014 		float leftMin = fTextRect.left - fLayoutData->leftInset;
5015 		if (bounds.left + scrollBy.x < leftMin)
5016 			scrollBy.x = leftMin - bounds.left;
5017 	}
5018 
5019 	if (CountLines() > 1) {
5020 		// scroll in Y only if multiple lines!
5021 		if (fWhere.y > bounds.bottom)
5022 			scrollBy.y = fWhere.y - bounds.bottom;
5023 		else if (fWhere.y < bounds.top)
5024 			scrollBy.y = fWhere.y - bounds.top; // negative value
5025 
5026 		// prevent from scrolling out of view
5027 		if (scrollBy.y != 0.0) {
5028 			float bottomMax = fTextRect.bottom + fLayoutData->bottomInset;
5029 			if (bounds.bottom + scrollBy.y > bottomMax)
5030 				scrollBy.y = bottomMax - bounds.bottom;
5031 			float topMin = fTextRect.top - fLayoutData->topInset;
5032 			if (bounds.top + scrollBy.y < topMin)
5033 				scrollBy.y = topMin - bounds.top;
5034 		}
5035 	}
5036 
5037 	if (scrollBy != B_ORIGIN)
5038 		ScrollBy(scrollBy.x, scrollBy.y);
5039 }
5040 
5041 
5042 //!	Updates the scrollbars associated with the object (if any).
5043 void
5044 BTextView::_UpdateScrollbars()
5045 {
5046 	BRect bounds(Bounds());
5047 	BScrollBar* horizontalScrollBar = ScrollBar(B_HORIZONTAL);
5048  	BScrollBar* verticalScrollBar = ScrollBar(B_VERTICAL);
5049 
5050 	// do we have a horizontal scroll bar?
5051 	if (horizontalScrollBar != NULL) {
5052 		long viewWidth = bounds.IntegerWidth();
5053 		long dataWidth = (long)ceilf(fTextRect.IntegerWidth()
5054 			+ fLayoutData->leftInset + fLayoutData->rightInset);
5055 
5056 		long maxRange = dataWidth - viewWidth;
5057 		maxRange = std::max(maxRange, 0l);
5058 
5059 		horizontalScrollBar->SetRange(0, (float)maxRange);
5060 		horizontalScrollBar->SetProportion((float)viewWidth
5061 			/ (float)dataWidth);
5062 		horizontalScrollBar->SetSteps(kHorizontalScrollBarStep,
5063 			dataWidth / 10);
5064 	}
5065 
5066 	// how about a vertical scroll bar?
5067 	if (verticalScrollBar != NULL) {
5068 		long viewHeight = bounds.IntegerHeight();
5069 		long dataHeight = (long)ceilf(fLayoutData->topInset
5070 			+ fTextRect.IntegerHeight() + fLayoutData->bottomInset);
5071 
5072 		long maxRange = dataHeight - viewHeight;
5073 		maxRange = std::max(maxRange, 0l);
5074 
5075 		verticalScrollBar->SetRange(0, maxRange);
5076 		verticalScrollBar->SetProportion((float)viewHeight
5077 			/ (float)dataHeight);
5078 		verticalScrollBar->SetSteps(kVerticalScrollBarStep,
5079 			viewHeight);
5080 	}
5081 }
5082 
5083 
5084 //!	Scrolls by the given offsets
5085 void
5086 BTextView::_ScrollBy(float horizontal, float vertical)
5087 {
5088 	BRect bounds = Bounds();
5089 	_ScrollTo(bounds.left + horizontal, bounds.top + vertical);
5090 }
5091 
5092 
5093 //!	Scrolls to the given position, making sure not to scroll out of bounds.
5094 void
5095 BTextView::_ScrollTo(float x, float y)
5096 {
5097 	BRect bounds = Bounds();
5098 	long viewWidth = bounds.IntegerWidth();
5099 	long viewHeight = bounds.IntegerHeight();
5100 
5101 	if (x > fTextRect.right - viewWidth)
5102 		x = fTextRect.right - viewWidth;
5103 	if (x < 0.0)
5104 		x = 0.0;
5105 
5106 	if (y > fTextRect.bottom + fLayoutData->bottomInset - viewHeight)
5107 		y = fTextRect.bottom + fLayoutData->bottomInset - viewHeight;
5108 	if (y < 0.0)
5109 		y = 0.0;
5110 
5111 	ScrollTo(x, y);
5112 }
5113 
5114 
5115 //!	Autoresizes the view to fit the contained text.
5116 void
5117 BTextView::_AutoResize(bool redraw)
5118 {
5119 	if (!fResizable || fContainerView == NULL)
5120 		return;
5121 
5122 	// NOTE: This container view thing is only used by Tracker.
5123 	// move container view if not left aligned
5124 	float oldWidth = Bounds().Width();
5125 	float newWidth = fLayoutData->leftInset + fTextRect.Width()
5126 		+ fLayoutData->rightInset;
5127 	float right = oldWidth - newWidth;
5128 
5129 	if (fAlignment == B_ALIGN_CENTER)
5130 		fContainerView->MoveBy(roundf(right / 2), 0);
5131 	else if (fAlignment == B_ALIGN_RIGHT)
5132 		fContainerView->MoveBy(right, 0);
5133 
5134 	// resize container view
5135 	float grow = newWidth - oldWidth;
5136 	fContainerView->ResizeBy(grow, 0);
5137 
5138 	// reposition text view
5139 	fTextRect.OffsetTo(fLayoutData->leftInset, fLayoutData->topInset);
5140 
5141 	// scroll rect to start, there is room for full text
5142 	ScrollToOffset(0);
5143 
5144 	if (redraw)
5145 		_RequestDrawLines(0, 0);
5146 }
5147 
5148 
5149 //!	Creates a new offscreen BBitmap with an associated BView.
5150 void
5151 BTextView::_NewOffscreen(float padding)
5152 {
5153 	if (fOffscreen != NULL)
5154 		_DeleteOffscreen();
5155 
5156 #if USE_DOUBLEBUFFERING
5157 	BRect bitmapRect(0, 0, fTextRect.Width() + padding, fTextRect.Height());
5158 	fOffscreen = new BBitmap(bitmapRect, fColorSpace, true, false);
5159 	if (fOffscreen != NULL && fOffscreen->Lock()) {
5160 		BView* bufferView = new BView(bitmapRect, "drawing view", 0, 0);
5161 		fOffscreen->AddChild(bufferView);
5162 		fOffscreen->Unlock();
5163 	}
5164 #endif
5165 }
5166 
5167 
5168 //!	Deletes the textview's offscreen bitmap, if any.
5169 void
5170 BTextView::_DeleteOffscreen()
5171 {
5172 	if (fOffscreen != NULL && fOffscreen->Lock()) {
5173 		delete fOffscreen;
5174 		fOffscreen = NULL;
5175 	}
5176 }
5177 
5178 
5179 /*!	Creates a new offscreen bitmap, highlight the selection, and set the
5180 	cursor to \c B_CURSOR_I_BEAM.
5181 */
5182 void
5183 BTextView::_Activate()
5184 {
5185 	fActive = true;
5186 
5187 	// Create a new offscreen BBitmap
5188 	_NewOffscreen();
5189 
5190 	if (fSelStart != fSelEnd) {
5191 		if (fSelectable)
5192 			Highlight(fSelStart, fSelEnd);
5193 	} else
5194 		_ShowCaret();
5195 
5196 	BPoint where;
5197 	uint32 buttons;
5198 	GetMouse(&where, &buttons, false);
5199 	if (Bounds().Contains(where))
5200 		_TrackMouse(where, NULL);
5201 
5202 	if (Window() != NULL) {
5203 		BMessage* message;
5204 
5205 		if (!Window()->HasShortcut(B_LEFT_ARROW, B_COMMAND_KEY)
5206 			&& !Window()->HasShortcut(B_RIGHT_ARROW, B_COMMAND_KEY)) {
5207 			message = new BMessage(kMsgNavigateArrow);
5208 			message->AddInt32("key", B_LEFT_ARROW);
5209 			message->AddInt32("modifiers", B_COMMAND_KEY);
5210 			Window()->AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, message, this);
5211 
5212 			message = new BMessage(kMsgNavigateArrow);
5213 			message->AddInt32("key", B_RIGHT_ARROW);
5214 			message->AddInt32("modifiers", B_COMMAND_KEY);
5215 			Window()->AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, message, this);
5216 
5217 			fInstalledNavigateCommandWordwiseShortcuts = true;
5218 		}
5219 		if (!Window()->HasShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY)
5220 			&& !Window()->HasShortcut(B_RIGHT_ARROW,
5221 				B_COMMAND_KEY | B_SHIFT_KEY)) {
5222 			message = new BMessage(kMsgNavigateArrow);
5223 			message->AddInt32("key", B_LEFT_ARROW);
5224 			message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
5225 			Window()->AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY,
5226 				message, this);
5227 
5228 			message = new BMessage(kMsgNavigateArrow);
5229 			message->AddInt32("key", B_RIGHT_ARROW);
5230 			message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
5231 			Window()->AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY,
5232 				message, this);
5233 
5234 			fInstalledSelectCommandWordwiseShortcuts = true;
5235 		}
5236 		if (!Window()->HasShortcut(B_DELETE, B_COMMAND_KEY)
5237 			&& !Window()->HasShortcut(B_BACKSPACE, B_COMMAND_KEY)) {
5238 			message = new BMessage(kMsgRemoveWord);
5239 			message->AddInt32("key", B_DELETE);
5240 			message->AddInt32("modifiers", B_COMMAND_KEY);
5241 			Window()->AddShortcut(B_DELETE, B_COMMAND_KEY, message, this);
5242 
5243 			message = new BMessage(kMsgRemoveWord);
5244 			message->AddInt32("key", B_BACKSPACE);
5245 			message->AddInt32("modifiers", B_COMMAND_KEY);
5246 			Window()->AddShortcut(B_BACKSPACE, B_COMMAND_KEY, message, this);
5247 
5248 			fInstalledRemoveCommandWordwiseShortcuts = true;
5249 		}
5250 
5251 		if (!Window()->HasShortcut(B_LEFT_ARROW, B_OPTION_KEY)
5252 			&& !Window()->HasShortcut(B_RIGHT_ARROW, B_OPTION_KEY)) {
5253 			message = new BMessage(kMsgNavigateArrow);
5254 			message->AddInt32("key", B_LEFT_ARROW);
5255 			message->AddInt32("modifiers", B_OPTION_KEY);
5256 			Window()->AddShortcut(B_LEFT_ARROW, B_OPTION_KEY, message, this);
5257 
5258 			message = new BMessage(kMsgNavigateArrow);
5259 			message->AddInt32("key", B_RIGHT_ARROW);
5260 			message->AddInt32("modifiers", B_OPTION_KEY);
5261 			Window()->AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY, message, this);
5262 
5263 			fInstalledNavigateOptionWordwiseShortcuts = true;
5264 		}
5265 		if (!Window()->HasShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY)
5266 			&& !Window()->HasShortcut(B_RIGHT_ARROW,
5267 				B_OPTION_KEY | B_SHIFT_KEY)) {
5268 			message = new BMessage(kMsgNavigateArrow);
5269 			message->AddInt32("key", B_LEFT_ARROW);
5270 			message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
5271 			Window()->AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
5272 				message, this);
5273 
5274 			message = new BMessage(kMsgNavigateArrow);
5275 			message->AddInt32("key", B_RIGHT_ARROW);
5276 			message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
5277 			Window()->AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
5278 				message, this);
5279 
5280 			fInstalledSelectOptionWordwiseShortcuts = true;
5281 		}
5282 		if (!Window()->HasShortcut(B_DELETE, B_OPTION_KEY)
5283 			&& !Window()->HasShortcut(B_BACKSPACE, B_OPTION_KEY)) {
5284 			message = new BMessage(kMsgRemoveWord);
5285 			message->AddInt32("key", B_DELETE);
5286 			message->AddInt32("modifiers", B_OPTION_KEY);
5287 			Window()->AddShortcut(B_DELETE, B_OPTION_KEY, message, this);
5288 
5289 			message = new BMessage(kMsgRemoveWord);
5290 			message->AddInt32("key", B_BACKSPACE);
5291 			message->AddInt32("modifiers", B_OPTION_KEY);
5292 			Window()->AddShortcut(B_BACKSPACE, B_OPTION_KEY, message, this);
5293 
5294 			fInstalledRemoveOptionWordwiseShortcuts = true;
5295 		}
5296 
5297 		if (!Window()->HasShortcut(B_UP_ARROW, B_OPTION_KEY)
5298 			&& !Window()->HasShortcut(B_DOWN_ARROW, B_OPTION_KEY)) {
5299 			message = new BMessage(kMsgNavigateArrow);
5300 			message->AddInt32("key", B_UP_ARROW);
5301 			message->AddInt32("modifiers", B_OPTION_KEY);
5302 			Window()->AddShortcut(B_UP_ARROW, B_OPTION_KEY, message, this);
5303 
5304 			message = new BMessage(kMsgNavigateArrow);
5305 			message->AddInt32("key", B_DOWN_ARROW);
5306 			message->AddInt32("modifiers", B_OPTION_KEY);
5307 			Window()->AddShortcut(B_DOWN_ARROW, B_OPTION_KEY, message, this);
5308 
5309 			fInstalledNavigateOptionLinewiseShortcuts = true;
5310 		}
5311 		if (!Window()->HasShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY)
5312 			&& !Window()->HasShortcut(B_DOWN_ARROW,
5313 				B_OPTION_KEY | B_SHIFT_KEY)) {
5314 			message = new BMessage(kMsgNavigateArrow);
5315 			message->AddInt32("key", B_UP_ARROW);
5316 			message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
5317 			Window()->AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
5318 				message, this);
5319 
5320 			message = new BMessage(kMsgNavigateArrow);
5321 			message->AddInt32("key", B_DOWN_ARROW);
5322 			message->AddInt32("modifiers", B_OPTION_KEY | B_SHIFT_KEY);
5323 			Window()->AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_SHIFT_KEY,
5324 				message, this);
5325 
5326 			fInstalledSelectOptionLinewiseShortcuts = true;
5327 		}
5328 
5329 		if (!Window()->HasShortcut(B_HOME, B_COMMAND_KEY)
5330 			&& !Window()->HasShortcut(B_END, B_COMMAND_KEY)) {
5331 			message = new BMessage(kMsgNavigatePage);
5332 			message->AddInt32("key", B_HOME);
5333 			message->AddInt32("modifiers", B_COMMAND_KEY);
5334 			Window()->AddShortcut(B_HOME, B_COMMAND_KEY, message, this);
5335 
5336 			message = new BMessage(kMsgNavigatePage);
5337 			message->AddInt32("key", B_END);
5338 			message->AddInt32("modifiers", B_COMMAND_KEY);
5339 			Window()->AddShortcut(B_END, B_COMMAND_KEY, message, this);
5340 
5341 			fInstalledNavigateHomeEndDocwiseShortcuts = true;
5342 		}
5343 		if (!Window()->HasShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY)
5344 			&& !Window()->HasShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY)) {
5345 			message = new BMessage(kMsgNavigatePage);
5346 			message->AddInt32("key", B_HOME);
5347 			message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
5348 			Window()->AddShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY,
5349 				message, this);
5350 
5351 			message = new BMessage(kMsgNavigatePage);
5352 			message->AddInt32("key", B_END);
5353 			message->AddInt32("modifiers", B_COMMAND_KEY | B_SHIFT_KEY);
5354 			Window()->AddShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY,
5355 				message, this);
5356 
5357 			fInstalledSelectHomeEndDocwiseShortcuts = true;
5358 		}
5359 	}
5360 }
5361 
5362 
5363 //!	Unhilights the selection, set the cursor to \c B_CURSOR_SYSTEM_DEFAULT.
5364 void
5365 BTextView::_Deactivate()
5366 {
5367 	fActive = false;
5368 
5369 	_CancelInputMethod();
5370 	_DeleteOffscreen();
5371 
5372 	if (fSelStart != fSelEnd) {
5373 		if (fSelectable)
5374 			Highlight(fSelStart, fSelEnd);
5375 	} else
5376 		_HideCaret();
5377 
5378 	if (Window() != NULL) {
5379 		if (fInstalledNavigateCommandWordwiseShortcuts) {
5380 			Window()->RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY);
5381 			Window()->RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY);
5382 			fInstalledNavigateCommandWordwiseShortcuts = false;
5383 		}
5384 		if (fInstalledSelectCommandWordwiseShortcuts) {
5385 			Window()->RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY);
5386 			Window()->RemoveShortcut(B_RIGHT_ARROW,
5387 				B_COMMAND_KEY | B_SHIFT_KEY);
5388 			fInstalledSelectCommandWordwiseShortcuts = false;
5389 		}
5390 		if (fInstalledRemoveCommandWordwiseShortcuts) {
5391 			Window()->RemoveShortcut(B_DELETE, B_COMMAND_KEY);
5392 			Window()->RemoveShortcut(B_BACKSPACE, B_COMMAND_KEY);
5393 			fInstalledRemoveCommandWordwiseShortcuts = false;
5394 		}
5395 
5396 		if (fInstalledNavigateOptionWordwiseShortcuts) {
5397 			Window()->RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY);
5398 			Window()->RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY);
5399 			fInstalledNavigateOptionWordwiseShortcuts = false;
5400 		}
5401 		if (fInstalledSelectOptionWordwiseShortcuts) {
5402 			Window()->RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
5403 			Window()->RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
5404 			fInstalledSelectOptionWordwiseShortcuts = false;
5405 		}
5406 		if (fInstalledRemoveOptionWordwiseShortcuts) {
5407 			Window()->RemoveShortcut(B_DELETE, B_OPTION_KEY);
5408 			Window()->RemoveShortcut(B_BACKSPACE, B_OPTION_KEY);
5409 			fInstalledRemoveOptionWordwiseShortcuts = false;
5410 		}
5411 
5412 		if (fInstalledNavigateOptionLinewiseShortcuts) {
5413 			Window()->RemoveShortcut(B_UP_ARROW, B_OPTION_KEY);
5414 			Window()->RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY);
5415 			fInstalledNavigateOptionLinewiseShortcuts = false;
5416 		}
5417 		if (fInstalledSelectOptionLinewiseShortcuts) {
5418 			Window()->RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
5419 			Window()->RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_SHIFT_KEY);
5420 			fInstalledSelectOptionLinewiseShortcuts = false;
5421 		}
5422 
5423 		if (fInstalledNavigateHomeEndDocwiseShortcuts) {
5424 			Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY);
5425 			Window()->RemoveShortcut(B_END, B_COMMAND_KEY);
5426 			fInstalledNavigateHomeEndDocwiseShortcuts = false;
5427 		}
5428 		if (fInstalledSelectHomeEndDocwiseShortcuts) {
5429 			Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY | B_SHIFT_KEY);
5430 			Window()->RemoveShortcut(B_END, B_COMMAND_KEY | B_SHIFT_KEY);
5431 			fInstalledSelectHomeEndDocwiseShortcuts = false;
5432 		}
5433 	}
5434 }
5435 
5436 
5437 /*!	Changes the passed in font to be displayable by the object.
5438 
5439 	Set font rotation to 0, removes any font flag, set font spacing
5440 	to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8.
5441 */
5442 void
5443 BTextView::_NormalizeFont(BFont* font)
5444 {
5445 	if (font) {
5446 		font->SetRotation(0.0f);
5447 		font->SetFlags(0);
5448 		font->SetSpacing(B_BITMAP_SPACING);
5449 		font->SetEncoding(B_UNICODE_UTF8);
5450 	}
5451 }
5452 
5453 
5454 void
5455 BTextView::_SetRunArray(int32 startOffset, int32 endOffset,
5456 	const text_run_array* runs)
5457 {
5458 	const int32 numStyles = runs->count;
5459 	if (numStyles > 0) {
5460 		const text_run* theRun = &runs->runs[0];
5461 		for (int32 index = 0; index < numStyles; index++) {
5462 			int32 fromOffset = theRun->offset + startOffset;
5463 			int32 toOffset = endOffset;
5464 			if (index + 1 < numStyles) {
5465 				toOffset = (theRun + 1)->offset + startOffset;
5466 				toOffset = (toOffset > endOffset) ? endOffset : toOffset;
5467 			}
5468 
5469 			_ApplyStyleRange(fromOffset, toOffset, B_FONT_ALL, &theRun->font,
5470 				&theRun->color, false);
5471 
5472 			theRun++;
5473 		}
5474 		fStyles->InvalidateNullStyle();
5475 	}
5476 }
5477 
5478 
5479 /*!	Returns the character class of the character at the given offset.
5480 
5481 	\param offset The offset where the wanted character can be found.
5482 
5483 	\return A value which represents the character's classification.
5484 */
5485 uint32
5486 BTextView::_CharClassification(int32 offset) const
5487 {
5488 	// TODO: Should check against a list of characters containing also
5489 	// japanese word breakers.
5490 	// And what about other languages ? Isn't there a better way to check
5491 	// for separator characters ?
5492 	// Andrew suggested to have a look at UnicodeBlockObject.h
5493 	switch (fText->RealCharAt(offset)) {
5494 		case '\0':
5495 			return CHAR_CLASS_END_OF_TEXT;
5496 
5497 		case B_SPACE:
5498 		case B_TAB:
5499 		case B_ENTER:
5500 			return CHAR_CLASS_WHITESPACE;
5501 
5502 		case '=':
5503 		case '+':
5504 		case '@':
5505 		case '#':
5506 		case '$':
5507 		case '%':
5508 		case '^':
5509 		case '&':
5510 		case '*':
5511 		case '\\':
5512 		case '|':
5513 		case '<':
5514 		case '>':
5515 		case '/':
5516 		case '~':
5517 			return CHAR_CLASS_GRAPHICAL;
5518 
5519 		case '\'':
5520 		case '"':
5521 			return CHAR_CLASS_QUOTE;
5522 
5523 		case ',':
5524 		case '.':
5525 		case '?':
5526 		case '!':
5527 		case ';':
5528 		case ':':
5529 		case '-':
5530 			return CHAR_CLASS_PUNCTUATION;
5531 
5532 		case '(':
5533 		case '[':
5534 		case '{':
5535 			return CHAR_CLASS_PARENS_OPEN;
5536 
5537 		case ')':
5538 		case ']':
5539 		case '}':
5540 			return CHAR_CLASS_PARENS_CLOSE;
5541 
5542 		default:
5543 			return CHAR_CLASS_DEFAULT;
5544 	}
5545 }
5546 
5547 
5548 /*!	Returns the offset of the next UTF-8 character.
5549 
5550 	\param offset The offset where to start looking.
5551 
5552 	\return The offset of the next UTF-8 character.
5553 */
5554 int32
5555 BTextView::_NextInitialByte(int32 offset) const
5556 {
5557 	if (offset >= fText->Length())
5558 		return offset;
5559 
5560 	for (++offset; (ByteAt(offset) & 0xC0) == 0x80; ++offset)
5561 		;
5562 
5563 	return offset;
5564 }
5565 
5566 
5567 /*!	Returns the offset of the previous UTF-8 character.
5568 
5569 	\param offset The offset where to start looking.
5570 
5571 	\return The offset of the previous UTF-8 character.
5572 */
5573 int32
5574 BTextView::_PreviousInitialByte(int32 offset) const
5575 {
5576 	if (offset <= 0)
5577 		return 0;
5578 
5579 	int32 count = 6;
5580 
5581 	for (--offset; offset > 0 && count; --offset, --count) {
5582 		if ((ByteAt(offset) & 0xC0) != 0x80)
5583 			break;
5584 	}
5585 
5586 	return count ? offset : 0;
5587 }
5588 
5589 
5590 bool
5591 BTextView::_GetProperty(BMessage* message, BMessage* specifier,
5592 	const char* property, BMessage* reply)
5593 {
5594 	CALLED();
5595 	if (strcmp(property, "selection") == 0) {
5596 		reply->what = B_REPLY;
5597 		reply->AddInt32("result", fSelStart);
5598 		reply->AddInt32("result", fSelEnd);
5599 		reply->AddInt32("error", B_OK);
5600 
5601 		return true;
5602 	} else if (strcmp(property, "Text") == 0) {
5603 		if (IsTypingHidden()) {
5604 			// Do not allow stealing passwords via scripting
5605 			beep();
5606 			return false;
5607 		}
5608 
5609 		int32 index, range;
5610 		specifier->FindInt32("index", &index);
5611 		specifier->FindInt32("range", &range);
5612 
5613 		char* buffer = new char[range + 1];
5614 		GetText(index, range, buffer);
5615 
5616 		reply->what = B_REPLY;
5617 		reply->AddString("result", buffer);
5618 		reply->AddInt32("error", B_OK);
5619 
5620 		delete[] buffer;
5621 
5622 		return true;
5623 	} else if (strcmp(property, "text_run_array") == 0)
5624 		return false;
5625 
5626 	return false;
5627 }
5628 
5629 
5630 bool
5631 BTextView::_SetProperty(BMessage* message, BMessage* specifier,
5632 	const char* property, BMessage* reply)
5633 {
5634 	CALLED();
5635 	if (strcmp(property, "selection") == 0) {
5636 		int32 index, range;
5637 
5638 		specifier->FindInt32("index", &index);
5639 		specifier->FindInt32("range", &range);
5640 
5641 		Select(index, index + range);
5642 
5643 		reply->what = B_REPLY;
5644 		reply->AddInt32("error", B_OK);
5645 
5646 		return true;
5647 	} else if (strcmp(property, "Text") == 0) {
5648 		int32 index, range;
5649 		specifier->FindInt32("index", &index);
5650 		specifier->FindInt32("range", &range);
5651 
5652 		const char* buffer = NULL;
5653 		if (message->FindString("data", &buffer) == B_OK) {
5654 			InsertText(buffer, range, index, NULL);
5655 			_Refresh(index, index + range);
5656 		} else {
5657 			DeleteText(index, index + range);
5658 			_Refresh(index, index);
5659 		}
5660 
5661 		reply->what = B_REPLY;
5662 		reply->AddInt32("error", B_OK);
5663 
5664 		return true;
5665 	} else if (strcmp(property, "text_run_array") == 0)
5666 		return false;
5667 
5668 	return false;
5669 }
5670 
5671 
5672 bool
5673 BTextView::_CountProperties(BMessage* message, BMessage* specifier,
5674 	const char* property, BMessage* reply)
5675 {
5676 	CALLED();
5677 	if (strcmp(property, "Text") == 0) {
5678 		reply->what = B_REPLY;
5679 		reply->AddInt32("result", fText->Length());
5680 		reply->AddInt32("error", B_OK);
5681 		return true;
5682 	}
5683 
5684 	return false;
5685 }
5686 
5687 
5688 //!	Called when the object receives a \c B_INPUT_METHOD_CHANGED message.
5689 void
5690 BTextView::_HandleInputMethodChanged(BMessage* message)
5691 {
5692 	// TODO: block input if not editable (Andrew)
5693 	ASSERT(fInline != NULL);
5694 
5695 	const char* string = NULL;
5696 	if (message->FindString("be:string", &string) < B_OK || string == NULL)
5697 		return;
5698 
5699 	_HideCaret();
5700 
5701 	if (IsFocus())
5702 		be_app->ObscureCursor();
5703 
5704 	// If we find the "be:confirmed" boolean (and the boolean is true),
5705 	// it means it's over for now, so the current InlineInput object
5706 	// should become inactive. We will probably receive a
5707 	// B_INPUT_METHOD_STOPPED message after this one.
5708 	bool confirmed;
5709 	if (message->FindBool("be:confirmed", &confirmed) != B_OK)
5710 		confirmed = false;
5711 
5712 	// Delete the previously inserted text (if any)
5713 	if (fInline->IsActive()) {
5714 		const int32 oldOffset = fInline->Offset();
5715 		DeleteText(oldOffset, oldOffset + fInline->Length());
5716 		if (confirmed)
5717 			fInline->SetActive(false);
5718 		fCaretOffset = fSelStart = fSelEnd = oldOffset;
5719 	}
5720 
5721 	const int32 stringLen = strlen(string);
5722 
5723 	fInline->SetOffset(fSelStart);
5724 	fInline->SetLength(stringLen);
5725 	fInline->ResetClauses();
5726 
5727 	if (!confirmed && !fInline->IsActive())
5728 		fInline->SetActive(true);
5729 
5730 	// Get the clauses, and pass them to the InlineInput object
5731 	// TODO: Find out if what we did it's ok, currently we don't consider
5732 	// clauses at all, while the bebook says we should; though the visual
5733 	// effect we obtained seems correct. Weird.
5734 	int32 clauseCount = 0;
5735 	int32 clauseStart;
5736 	int32 clauseEnd;
5737 	while (message->FindInt32("be:clause_start", clauseCount, &clauseStart)
5738 			== B_OK
5739 		&& message->FindInt32("be:clause_end", clauseCount, &clauseEnd)
5740 			== B_OK) {
5741 		if (!fInline->AddClause(clauseStart, clauseEnd))
5742 			break;
5743 		clauseCount++;
5744 	}
5745 
5746 	if (confirmed) {
5747 		_Refresh(fSelStart, fSelEnd, fSelEnd);
5748 		_ShowCaret();
5749 
5750 		// now we need to feed ourselves the individual characters as if the
5751 		// user would have pressed them now - this lets KeyDown() pick out all
5752 		// the special characters like B_BACKSPACE, cursor keys and the like:
5753 		const char* currPos = string;
5754 		const char* prevPos = currPos;
5755 		while (*currPos != '\0') {
5756 			if ((*currPos & 0xC0) == 0xC0) {
5757 				// found the start of an UTF-8 char, we collect while it lasts
5758 				++currPos;
5759 				while ((*currPos & 0xC0) == 0x80)
5760 					++currPos;
5761 			} else if ((*currPos & 0xC0) == 0x80) {
5762 				// illegal: character starts with utf-8 intermediate byte,
5763 				// skip it
5764 				prevPos = ++currPos;
5765 			} else {
5766 				// single byte character/code, just feed that
5767 				++currPos;
5768 			}
5769 			KeyDown(prevPos, currPos - prevPos);
5770 			prevPos = currPos;
5771 		}
5772 
5773 		_Refresh(fSelStart, fSelEnd, fSelEnd);
5774 	} else {
5775 		// temporarily show transient state of inline input
5776 		int32 selectionStart = 0;
5777 		int32 selectionEnd = 0;
5778 		message->FindInt32("be:selection", 0, &selectionStart);
5779 		message->FindInt32("be:selection", 1, &selectionEnd);
5780 
5781 		fInline->SetSelectionOffset(selectionStart);
5782 		fInline->SetSelectionLength(selectionEnd - selectionStart);
5783 
5784 		const int32 inlineOffset = fInline->Offset();
5785 		InsertText(string, stringLen, fSelStart, NULL);
5786 
5787 		_Refresh(inlineOffset, fSelEnd, fSelEnd);
5788 		_ShowCaret();
5789 	}
5790 }
5791 
5792 
5793 /*!	Called when the object receives a \c B_INPUT_METHOD_LOCATION_REQUEST
5794 	message.
5795 */
5796 void
5797 BTextView::_HandleInputMethodLocationRequest()
5798 {
5799 	ASSERT(fInline != NULL);
5800 
5801 	int32 offset = fInline->Offset();
5802 	const int32 limit = offset + fInline->Length();
5803 
5804 	BMessage message(B_INPUT_METHOD_EVENT);
5805 	message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
5806 
5807 	// Add the location of the UTF8 characters
5808 	while (offset < limit) {
5809 		float height;
5810 		BPoint where = PointAt(offset, &height);
5811 		ConvertToScreen(&where);
5812 
5813 		message.AddPoint("be:location_reply", where);
5814 		message.AddFloat("be:height_reply", height);
5815 
5816 		offset = _NextInitialByte(offset);
5817 	}
5818 
5819 	fInline->Method()->SendMessage(&message);
5820 }
5821 
5822 
5823 //!	Tells the Input Server method add-on to stop the current transaction.
5824 void
5825 BTextView::_CancelInputMethod()
5826 {
5827 	if (!fInline)
5828 		return;
5829 
5830 	InlineInput* inlineInput = fInline;
5831 	fInline = NULL;
5832 
5833 	if (inlineInput->IsActive() && Window()) {
5834 		_Refresh(inlineInput->Offset(), fText->Length()
5835 			- inlineInput->Offset());
5836 
5837 		BMessage message(B_INPUT_METHOD_EVENT);
5838 		message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
5839 		inlineInput->Method()->SendMessage(&message);
5840 	}
5841 
5842 	delete inlineInput;
5843 }
5844 
5845 
5846 /*!	Returns the line number of the character at the given \a offset.
5847 
5848 	\note This will never return the last line (use LineAt() if you
5849 	      need to be correct about that.) N.B.
5850 
5851 	\param offset The offset of the wanted character.
5852 
5853 	\return The line number of the character at the given \a offset as an int32.
5854 */
5855 int32
5856 BTextView::_LineAt(int32 offset) const
5857 {
5858 	return fLines->OffsetToLine(offset);
5859 }
5860 
5861 
5862 /*!	Returns the line number that the given \a point is on.
5863 
5864 	\note This will never return the last line (use LineAt() if you
5865 	      need to be correct about that.) N.B.
5866 
5867 	\param point The \a point the get the line number of.
5868 
5869 	\return The line number of the given \a point as an int32.
5870 */
5871 int32
5872 BTextView::_LineAt(const BPoint& point) const
5873 {
5874 	return fLines->PixelToLine(point.y - fTextRect.top);
5875 }
5876 
5877 
5878 /*!	Returns whether or not the given \a offset is on the empty line at the end
5879 	of the buffer.
5880 */
5881 bool
5882 BTextView::_IsOnEmptyLastLine(int32 offset) const
5883 {
5884 	return (offset == fText->Length() && offset > 0
5885 		&& fText->RealCharAt(offset - 1) == B_ENTER);
5886 }
5887 
5888 
5889 void
5890 BTextView::_ApplyStyleRange(int32 fromOffset, int32 toOffset, uint32 mode,
5891 	const BFont* font, const rgb_color* color, bool syncNullStyle)
5892 {
5893 	BFont normalized;
5894 		// Declared before the if so it stays allocated until the call to
5895 		// SetStyleRange
5896 	if (font != NULL) {
5897 		// if a font has been given, normalize it
5898 		normalized = *font;
5899 		_NormalizeFont(&normalized);
5900 		font = &normalized;
5901 	}
5902 
5903 	if (!fStylable) {
5904 		// always apply font and color to full range for non-stylable textviews
5905 		fromOffset = 0;
5906 		toOffset = fText->Length();
5907 	}
5908 
5909 	if (syncNullStyle)
5910 		fStyles->SyncNullStyle(fromOffset);
5911 
5912 	fStyles->SetStyleRange(fromOffset, toOffset, fText->Length(), mode,
5913 		font, color);
5914 }
5915 
5916 
5917 float
5918 BTextView::_NullStyleHeight() const
5919 {
5920 	const BFont* font = NULL;
5921 	fStyles->GetNullStyle(&font, NULL);
5922 
5923 	font_height fontHeight;
5924 	font->GetHeight(&fontHeight);
5925 	return ceilf(fontHeight.ascent + fontHeight.descent + 1);
5926 }
5927 
5928 
5929 void
5930 BTextView::_ShowContextMenu(BPoint where)
5931 {
5932 	bool isRedo;
5933 	undo_state state = UndoState(&isRedo);
5934 	bool isUndo = state != B_UNDO_UNAVAILABLE && !isRedo;
5935 
5936 	int32 start;
5937 	int32 finish;
5938 	GetSelection(&start, &finish);
5939 
5940 	bool canEdit = IsEditable();
5941 	int32 length = fText->Length();
5942 
5943 	BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
5944 
5945 	BLayoutBuilder::Menu<>(menu)
5946 		.AddItem(TRANSLATE("Undo"), B_UNDO/*, 'Z'*/)
5947 			.SetEnabled(canEdit && isUndo)
5948 		.AddItem(TRANSLATE("Redo"), B_UNDO/*, 'Z', B_SHIFT_KEY*/)
5949 			.SetEnabled(canEdit && isRedo)
5950 		.AddSeparator()
5951 		.AddItem(TRANSLATE("Cut"), B_CUT, 'X')
5952 			.SetEnabled(canEdit && start != finish)
5953 		.AddItem(TRANSLATE("Copy"), B_COPY, 'C')
5954 			.SetEnabled(start != finish)
5955 		.AddItem(TRANSLATE("Paste"), B_PASTE, 'V')
5956 			.SetEnabled(canEdit && be_clipboard->SystemCount() > 0)
5957 		.AddSeparator()
5958 		.AddItem(TRANSLATE("Select all"), B_SELECT_ALL, 'A')
5959 			.SetEnabled(!(start == 0 && finish == length))
5960 	;
5961 
5962 	menu->SetTargetForItems(this);
5963 	ConvertToScreen(&where);
5964 	menu->Go(where, true, true,	true);
5965 }
5966 
5967 
5968 void
5969 BTextView::_FilterDisallowedChars(char* text, ssize_t& length,
5970 	text_run_array* runArray)
5971 {
5972 	if (!fDisallowedChars)
5973 		return;
5974 
5975 	if (fDisallowedChars->IsEmpty() || !text)
5976 		return;
5977 
5978 	ssize_t stringIndex = 0;
5979 	if (runArray) {
5980 		ssize_t remNext = 0;
5981 
5982 		for (int i = 0; i < runArray->count; i++) {
5983 			runArray->runs[i].offset -= remNext;
5984 			while (stringIndex < runArray->runs[i].offset
5985 				&& stringIndex < length) {
5986 				if (fDisallowedChars->HasItem(
5987 					reinterpret_cast<void*>(text[stringIndex]))) {
5988 					memmove(text + stringIndex, text + stringIndex + 1,
5989 						length - stringIndex - 1);
5990 					length--;
5991 					runArray->runs[i].offset--;
5992 					remNext++;
5993 				} else
5994 					stringIndex++;
5995 			}
5996 		}
5997 	}
5998 
5999 	while (stringIndex < length) {
6000 		if (fDisallowedChars->HasItem(
6001 			reinterpret_cast<void*>(text[stringIndex]))) {
6002 			memmove(text + stringIndex, text + stringIndex + 1,
6003 				length - stringIndex - 1);
6004 			length--;
6005 		} else
6006 			stringIndex++;
6007 	}
6008 }
6009 
6010 
6011 void
6012 BTextView::_UpdateInsets(const BRect& rect)
6013 {
6014 	// do not update insets if SetInsets() was called
6015 	if (fLayoutData->overridden)
6016 		return;
6017 
6018 	const BRect& bounds = Bounds();
6019 
6020 	// we disallow negative insets, as they would cause parts of the
6021 	// text to be hidden
6022 	fLayoutData->leftInset = rect.left >= bounds.left
6023 		? rect.left - bounds.left : 0;
6024 	fLayoutData->topInset = rect.top >= bounds.top
6025 		? rect.top - bounds.top : 0;
6026 	fLayoutData->rightInset = bounds.right >= rect.right
6027 		? bounds.right - rect.right : 0;
6028 	fLayoutData->bottomInset = bounds.bottom >= rect.bottom
6029 		? bounds.bottom - rect.bottom : 0;
6030 
6031 	// only add default insets if text rect is set to bounds
6032 	if (rect == bounds && (fEditable || fSelectable)) {
6033 		float hPadding = be_control_look->DefaultLabelSpacing();
6034 		float hInset = floorf(hPadding / 2.0f);
6035 		float vInset = 1;
6036 		fLayoutData->leftInset += hInset;
6037 		fLayoutData->topInset += vInset;
6038 		fLayoutData->rightInset += hInset;
6039 		fLayoutData->bottomInset += vInset;
6040 	}
6041 }
6042 
6043 
6044 // #pragma mark - BTextView::TextTrackState
6045 
6046 
6047 BTextView::TextTrackState::TextTrackState(BMessenger messenger)
6048 	:
6049 	clickOffset(0),
6050 	shiftDown(false),
6051 	anchor(0),
6052 	selStart(0),
6053 	selEnd(0),
6054 	fRunner(NULL)
6055 {
6056 	BMessage message(_PING_);
6057 	const bigtime_t scrollSpeed = 25 * 1000;	// 40 scroll steps per second
6058 	fRunner = new (nothrow) BMessageRunner(messenger, &message, scrollSpeed);
6059 }
6060 
6061 
6062 BTextView::TextTrackState::~TextTrackState()
6063 {
6064 	delete fRunner;
6065 }
6066 
6067 
6068 void
6069 BTextView::TextTrackState::SimulateMouseMovement(BTextView* textView)
6070 {
6071 	BPoint where;
6072 	uint32 buttons;
6073 	// When the mouse cursor is still and outside the textview,
6074 	// no B_MOUSE_MOVED message are sent, obviously. But scrolling
6075 	// has to work neverthless, so we "fake" a MouseMoved() call here.
6076 	textView->GetMouse(&where, &buttons);
6077 	textView->_PerformMouseMoved(where, B_INSIDE_VIEW);
6078 }
6079 
6080 
6081 // #pragma mark - Binary ABI compat
6082 
6083 
6084 extern "C" void
6085 B_IF_GCC_2(InvalidateLayout__9BTextViewb,  _ZN9BTextView16InvalidateLayoutEb)(
6086 	BTextView* view, bool descendants)
6087 {
6088 	perform_data_layout_invalidated data;
6089 	data.descendants = descendants;
6090 
6091 	view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
6092 }
6093