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