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