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