xref: /haiku/src/kits/interface/TextView.cpp (revision 43711de9d2e6df4f0270c4b531d7b93c36e9b5fc)
1 /*
2  * Copyright 2001-2008, 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 (stefano.ceccherini@gmail.com)
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 			int32 numBytes = fSelEnd - fSelStart;
1237 			const char* text = fText->GetString(fSelStart, &numBytes);
1238 			clip->AddData("text/plain", B_MIME_TYPE, text, numBytes);
1239 
1240 			int32 size;
1241 			if (fStylable) {
1242 				text_run_array *runArray = RunArray(fSelStart, fSelEnd, &size);
1243 				clip->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
1244 					runArray, size);
1245 				FreeRunArray(runArray);
1246 			}
1247 			clipboard->Commit();
1248 		}
1249 		clipboard->Unlock();
1250 	}
1251 }
1252 
1253 
1254 /*! \brief Paste the text contained in the clipboard to the BTextView.
1255 	\param clipboard A pointer to the clipboard.
1256 */
1257 void
1258 BTextView::Paste(BClipboard *clipboard)
1259 {
1260 	CALLED();
1261 	_CancelInputMethod();
1262 
1263 	if (clipboard->Lock()) {
1264 		BMessage *clip = clipboard->Data();
1265 		if (clip != NULL) {
1266 			const char *text = NULL;
1267 			ssize_t len = 0;
1268 
1269 			if (clip->FindData("text/plain", B_MIME_TYPE,
1270 					(const void **)&text, &len) == B_OK) {
1271 				text_run_array *runArray = NULL;
1272 				ssize_t runLen = 0;
1273 
1274 				if (fStylable)
1275 					clip->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
1276 						(const void **)&runArray, &runLen);
1277 
1278 				if (fUndo) {
1279 					delete fUndo;
1280 					fUndo = new _BPasteUndoBuffer_(this, text, len, runArray, runLen);
1281 				}
1282 
1283 				if (fSelStart != fSelEnd)
1284 					Delete();
1285 
1286 				Insert(text, len, runArray);
1287 			}
1288 		}
1289 
1290 		clipboard->Unlock();
1291 	}
1292 }
1293 
1294 
1295 /*! \brief Deletes the currently selected text.
1296 */
1297 void
1298 BTextView::Clear()
1299 {
1300 	// We always check for fUndo != NULL (not only here),
1301 	// because when fUndo is NULL, undo is deactivated.
1302 	if (fUndo) {
1303 		delete fUndo;
1304 		fUndo = new _BClearUndoBuffer_(this);
1305 	}
1306 
1307 	Delete();
1308 }
1309 
1310 
1311 bool
1312 BTextView::AcceptsPaste(BClipboard *clipboard)
1313 {
1314 	bool result = false;
1315 
1316 	if (fEditable && clipboard && clipboard->Lock()) {
1317 		BMessage *data = clipboard->Data();
1318 		result = data && data->HasData("text/plain", B_MIME_TYPE);
1319 		clipboard->Unlock();
1320 	}
1321 
1322 	return result;
1323 }
1324 
1325 
1326 bool
1327 BTextView::AcceptsDrop(const BMessage *inMessage)
1328 {
1329 	if (fEditable && inMessage && inMessage->HasData("text/plain", B_MIME_TYPE))
1330 		return true;
1331 
1332 	return false;
1333 }
1334 
1335 
1336 /*! \brief Selects the text within the given offsets.
1337 	\param startOffset The offset of the text to select.
1338 	\param endOffset The offset where the text ends.
1339 */
1340 void
1341 BTextView::Select(int32 startOffset, int32 endOffset)
1342 {
1343 	CALLED();
1344 	if (!fSelectable)
1345 		return;
1346 
1347 	_CancelInputMethod();
1348 
1349 	// a negative selection?
1350 	if (startOffset > endOffset)
1351 		return;
1352 
1353 	// pin offsets at reasonable values
1354 	if (startOffset < 0)
1355 		startOffset = 0;
1356 	if (endOffset < 0)
1357 		endOffset = 0;
1358 	else if (endOffset > fText->Length())
1359 		endOffset = fText->Length();
1360 
1361 	// is the new selection any different from the current selection?
1362 	if (startOffset == fSelStart && endOffset == fSelEnd)
1363 		return;
1364 
1365 	fStyles->InvalidateNullStyle();
1366 
1367 	_HideCaret();
1368 
1369 	if (startOffset == endOffset) {
1370 		if (fSelStart != fSelEnd) {
1371 			// unhilite the selection
1372 			if (fActive)
1373 				Highlight(fSelStart, fSelEnd);
1374 		}
1375 		fSelStart = fSelEnd = fClickOffset = startOffset;
1376 	} else {
1377 		if (fActive) {
1378 			// draw only those ranges that are different
1379 			long start, end;
1380 			if (startOffset != fSelStart) {
1381 				// start of selection has changed
1382 				if (startOffset > fSelStart) {
1383 					start = fSelStart;
1384 					end = startOffset;
1385 				} else {
1386 					start = startOffset;
1387 					end = fSelStart;
1388 				}
1389 				Highlight(start, end);
1390 			}
1391 
1392 			if (endOffset != fSelEnd) {
1393 				// end of selection has changed
1394 				if (endOffset > fSelEnd) {
1395 					start = fSelEnd;
1396 					end = endOffset;
1397 				} else {
1398 					start = endOffset;
1399 					end = fSelEnd;
1400 				}
1401 				Highlight(start, end);
1402 			}
1403 		}
1404 		fSelStart = startOffset;
1405 		fSelEnd = fClickOffset = endOffset;
1406 	}
1407 }
1408 
1409 
1410 /*! \brief Selects all the text within the BTextView.
1411 */
1412 void
1413 BTextView::SelectAll()
1414 {
1415 	Select(0, fText->Length());
1416 }
1417 
1418 
1419 /*! \brief Gets the current selection.
1420 	\param outStart A pointer to an int32 which will contain the selection start's offset.
1421 	\param outEnd A pointer to an int32 which will contain the selection end's offset.
1422 */
1423 void
1424 BTextView::GetSelection(int32 *outStart, int32 *outEnd) const
1425 {
1426 	int32 start = 0, end = 0;
1427 
1428 	if (fSelectable) {
1429 		start = fSelStart;
1430 		end = fSelEnd;
1431 	}
1432 
1433 	if (outStart)
1434 		*outStart = start;
1435 	if (outEnd)
1436 		*outEnd = end;
1437 }
1438 
1439 
1440 void
1441 BTextView::SetFontAndColor(const BFont *inFont, uint32 inMode, const rgb_color *inColor)
1442 {
1443 	SetFontAndColor(fSelStart, fSelEnd, inFont, inMode, inColor);
1444 }
1445 
1446 
1447 void
1448 BTextView::SetFontAndColor(int32 startOffset, int32 endOffset, const BFont* font,
1449 	uint32 fontMode, const rgb_color* color)
1450 {
1451 	CALLED();
1452 
1453 	if (startOffset > endOffset)
1454 		return;
1455 
1456 	BFont newFont;
1457 	if (font != NULL) {
1458 		newFont = font;
1459 		_NormalizeFont(&newFont);
1460 	}
1461 
1462 	const int32 textLength = fText->Length();
1463 
1464 	if (!fStylable) {
1465 		// When the text view is not stylable, we always set the whole text's
1466 		// style and ignore the offsets
1467 		startOffset = 0;
1468 		endOffset = textLength;
1469 	}
1470 
1471 	// pin offsets at reasonable values
1472 	if (startOffset < 0)
1473 		startOffset = 0;
1474 	else if (startOffset > textLength)
1475 		startOffset = textLength;
1476 
1477 	if (endOffset < 0)
1478 		endOffset = 0;
1479 	else if (endOffset > textLength)
1480 		endOffset = textLength;
1481 
1482 	// add the style to the style buffer
1483 	fStyles->SetStyleRange(startOffset, endOffset, fText->Length(),
1484 		fontMode, &newFont, color);
1485 
1486 	if (fontMode & B_FONT_FAMILY_AND_STYLE || fontMode & B_FONT_SIZE) {
1487 		// recalc the line breaks and redraw with new style
1488 		_Refresh(startOffset, endOffset, startOffset != endOffset, false);
1489 	} else {
1490 		// the line breaks wont change, simply redraw
1491 		_DrawLines(LineAt(startOffset), LineAt(endOffset), startOffset, true);
1492 	}
1493 }
1494 
1495 
1496 void
1497 BTextView::GetFontAndColor(int32 inOffset, BFont *outFont, rgb_color *outColor) const
1498 {
1499 	fStyles->GetStyle(inOffset, outFont, outColor);
1500 }
1501 
1502 
1503 void
1504 BTextView::GetFontAndColor(BFont *outFont, uint32 *outMode, rgb_color *outColor, bool *outEqColor) const
1505 {
1506 	fStyles->ContinuousGetStyle(outFont, outMode, outColor, outEqColor, fSelStart, fSelEnd);
1507 }
1508 
1509 
1510 void
1511 BTextView::SetRunArray(int32 startOffset, int32 endOffset, const text_run_array *inRuns)
1512 {
1513 	CALLED();
1514 
1515 	_CancelInputMethod();
1516 
1517 	_SetRunArray(startOffset, endOffset, inRuns);
1518 
1519 	_Refresh(startOffset, endOffset, true, false);
1520 }
1521 
1522 
1523 /*! \brief Returns a RunArray for the text within the given offsets.
1524 	\param startOffset The offset where to start.
1525 	\param endOffset The offset where the wanted text ends.
1526 	\param outSize A pointer to an int32 which will contain the size
1527 		of the run array.
1528 	\return A text_run_array for the text in the given offsets.
1529 
1530 	The returned text_run_array belongs to the caller, so you better
1531 	free it as soon as you don't need it.
1532 */
1533 text_run_array *
1534 BTextView::RunArray(int32 startOffset, int32 endOffset, int32 *outSize) const
1535 {
1536 	STEStyleRange* styleRange = fStyles->GetStyleRange(startOffset, endOffset - 1);
1537 	if (styleRange == NULL)
1538 		return NULL;
1539 
1540 	text_run_array *runArray = AllocRunArray(styleRange->count, outSize);
1541 	if (runArray != NULL) {
1542 		for (int32 i = 0; i < runArray->count; i++) {
1543 			runArray->runs[i].offset = styleRange->runs[i].offset;
1544 			runArray->runs[i].font = styleRange->runs[i].style.font;
1545 			runArray->runs[i].color = styleRange->runs[i].style.color;
1546 		}
1547 	}
1548 
1549 	free(styleRange);
1550 
1551 	return runArray;
1552 }
1553 
1554 
1555 /*! \brief Returns the line number for the character at the given offset.
1556 	\param offset The offset of the wanted character.
1557 	\return A line number.
1558 */
1559 int32
1560 BTextView::LineAt(int32 offset) const
1561 {
1562 	return fLines->OffsetToLine(offset);
1563 }
1564 
1565 
1566 /*! \brief Returns the line number for the given point.
1567 	\param point A point.
1568 	\return A line number.
1569 */
1570 int32
1571 BTextView::LineAt(BPoint point) const
1572 {
1573 	return fLines->PixelToLine(point.y - fTextRect.top);
1574 }
1575 
1576 
1577 /*! \brief Returns the location of the character at the given offset.
1578 	\param inOffset The offset of the character.
1579 	\param outHeight Here the function will put the height of the character at the
1580 	given offset.
1581 	\return A BPoint which is the location of the character.
1582 */
1583 BPoint
1584 BTextView::PointAt(int32 inOffset, float *outHeight) const
1585 {
1586 	// TODO: Cleanup.
1587 	const int32 textLength = fText->Length();
1588 	int32 lineNum = LineAt(inOffset);
1589 	STELine* line = (*fLines)[lineNum];
1590 	float height = 0;
1591 
1592 	BPoint result;
1593 	result.x = 0.0;
1594 	result.y = line->origin + fTextRect.top;
1595 
1596 	// Handle the case where there is only one line
1597 	// (no text inserted)
1598 	// TODO: See if we can do this better
1599 	if (fStyles->NumRuns() == 0) {
1600 		const rgb_color *color = NULL;
1601 		const BFont *font = NULL;
1602 		fStyles->GetNullStyle(&font, &color);
1603 
1604 		font_height fontHeight;
1605 		font->GetHeight(&fontHeight);
1606 		height = fontHeight.ascent + fontHeight.descent;
1607 
1608 	} else {
1609 		height = (line + 1)->origin - line->origin;
1610 
1611 		// special case: go down one line if inOffset is a newline
1612 		if (inOffset == textLength && fText->RealCharAt(inOffset - 1) == B_ENTER) {
1613 			result.y += height;
1614 			height = LineHeight(CountLines() - 1);
1615 
1616 		} else {
1617 			int32 offset = line->offset;
1618 			int32 length = inOffset - line->offset;
1619 			int32 numBytes = length;
1620 			bool foundTab = false;
1621 			do {
1622 				foundTab = fText->FindChar(B_TAB, offset, &numBytes);
1623 				float width = _StyledWidth(offset, numBytes);
1624 				result.x += width;
1625 
1626 				if (foundTab) {
1627 					result.x += _ActualTabWidth(result.x);
1628 					numBytes++;
1629 				}
1630 
1631 				offset += numBytes;
1632 				length -= numBytes;
1633 				numBytes = length;
1634 			} while (foundTab && length > 0);
1635 		}
1636 	}
1637 
1638 	if (fAlignment != B_ALIGN_LEFT) {
1639 		float modifier = fTextRect.right - LineWidth(lineNum);
1640 		if (fAlignment == B_ALIGN_CENTER)
1641 			modifier /= 2;
1642 		result.x += modifier;
1643 	}
1644 	// convert from text rect coordinates
1645 	// NOTE: I didn't understand why "- 1.0"
1646 	// and it works only correct without it on Haiku app_server.
1647 	// Feel free to enlighten me though!
1648 	result.x += fTextRect.left;// - 1.0;
1649 
1650 	// round up
1651 	result.x = ceilf(result.x);
1652 	result.y = ceilf(result.y);
1653 	if (outHeight != NULL)
1654 		*outHeight = height;
1655 
1656 	return result;
1657 }
1658 
1659 
1660 /*! \brief Returns the offset for the given location.
1661 	\param point A BPoint which specify the wanted location.
1662 	\return The offset for the given point.
1663 */
1664 int32
1665 BTextView::OffsetAt(BPoint point) const
1666 {
1667 	const int32 textLength = fText->Length();
1668 
1669 	// should we even bother?
1670 	if (point.y >= fTextRect.bottom)
1671 		return textLength;
1672 	else if (point.y < fTextRect.top)
1673 		return 0;
1674 
1675 	int32 lineNum = LineAt(point);
1676 	STELine* line = (*fLines)[lineNum];
1677 
1678 	// special case: if point is within the text rect and PixelToLine()
1679 	// tells us that it's on the last line, but if point is actually
1680 	// lower than the bottom of the last line, return the last offset
1681 	// (can happen for newlines)
1682 	if (lineNum == (fLines->NumLines() - 1)) {
1683 		if (point.y >= ((line + 1)->origin + fTextRect.top))
1684 			return textLength;
1685 	}
1686 
1687 	// convert to text rect coordinates
1688 	if (fAlignment != B_ALIGN_LEFT) {
1689 		float lineWidth = fTextRect.right - LineWidth(lineNum);
1690 		if (fAlignment == B_ALIGN_CENTER)
1691 			lineWidth /= 2;
1692 		point.x -= lineWidth;
1693 	}
1694 
1695 	point.x -= fTextRect.left;
1696 	point.x = max_c(point.x, 0.0);
1697 
1698 	// TODO: The following code isn't very efficient because it always starts from the left end,
1699 	// so when the point is near the right end it's very slow.
1700 	int32 offset = line->offset;
1701 	const int32 limit = (line + 1)->offset;
1702 	float location = 0;
1703 	do {
1704 		const int32 nextInitial = _NextInitialByte(offset);
1705 		const int32 saveOffset = offset;
1706 		float width = 0;
1707 		if (ByteAt(offset) == B_TAB)
1708 			width = _ActualTabWidth(location);
1709 		else
1710 			width = _StyledWidth(saveOffset, nextInitial - saveOffset);
1711 		if (location + width > point.x) {
1712 			if (fabs(location + width - point.x) < fabs(location - point.x))
1713 				offset = nextInitial;
1714 			break;
1715 		}
1716 
1717 		location += width;
1718 		offset = nextInitial;
1719 	} while (offset < limit);
1720 
1721 	if (offset == (line + 1)->offset) {
1722 		// special case: newlines aren't visible
1723 		// return the offset of the character preceding the newline
1724 		if (ByteAt(offset - 1) == B_ENTER)
1725 			return --offset;
1726 
1727 		// special case: return the offset preceding any spaces that
1728 		// aren't at the end of the buffer
1729 		if (offset != textLength && ByteAt(offset - 1) == B_SPACE)
1730 			return --offset;
1731 	}
1732 
1733 	return offset;
1734 }
1735 
1736 
1737 /*! \brief Returns the offset of the given line.
1738 	\param line A line number.
1739 	\return The offset of the passed line.
1740 */
1741 int32
1742 BTextView::OffsetAt(int32 line) const
1743 {
1744 	if (line > fLines->NumLines())
1745 		return fText->Length();
1746 
1747 	return (*fLines)[line]->offset;
1748 }
1749 
1750 
1751 /*! \brief Looks for a sequence of character that qualifies as a word.
1752 	\param inOffset The offset where to start looking.
1753 	\param outFromOffset A pointer to an integer which will contain the starting
1754 		offset of the word.
1755 	\param outToOffset A pointer to an integer which will contain the ending
1756 		offset of the word.
1757 */
1758 void
1759 BTextView::FindWord(int32 inOffset, int32 *outFromOffset, int32 *outToOffset)
1760 {
1761 	int32 offset;
1762 	uint32 charType = _CharClassification(inOffset);
1763 
1764 	// check to the left
1765 	int32 previous;
1766 	for (offset = inOffset, previous = offset; offset > 0;
1767 				previous = _PreviousInitialByte(offset)) {
1768 		if (_CharClassification(previous) != charType)
1769 			break;
1770 		offset = previous;
1771 	}
1772 
1773 	if (outFromOffset)
1774 		*outFromOffset = offset;
1775 
1776 	// check to the right
1777 	int32 textLen = TextLength();
1778 	for (offset = inOffset; offset < textLen; offset = _NextInitialByte(offset)) {
1779 		if (_CharClassification(offset) != charType)
1780 			break;
1781 	}
1782 
1783 	if (outToOffset)
1784 		*outToOffset = offset;
1785 }
1786 
1787 
1788 /*! \brief Returns true if the character at the given offset can be the last character in a line.
1789 	\param offset The offset of the character.
1790 	\return true if the character can be the last of a line, false if not.
1791 */
1792 bool
1793 BTextView::CanEndLine(int32 offset)
1794 {
1795 	// TODO: Could be improved, the bebook says there are other checks to do
1796 	return (_CharClassification(offset) == B_SEPARATOR_CHARACTER);
1797 }
1798 
1799 
1800 /*! \brief Returns the width of the line at the given index.
1801 	\param lineNum A line index.
1802 */
1803 float
1804 BTextView::LineWidth(int32 lineNum) const
1805 {
1806 	if (lineNum < 0 || lineNum >= fLines->NumLines())
1807 		return 0;
1808 
1809 	STELine* line = (*fLines)[lineNum];
1810 	return _StyledWidth(line->offset, (line + 1)->offset - line->offset);
1811 }
1812 
1813 
1814 /*! \brief Returns the height of the line at the given index.
1815 	\param lineNum A line index.
1816 */
1817 float
1818 BTextView::LineHeight(int32 lineNum) const
1819 {
1820 	return TextHeight(lineNum, lineNum);
1821 }
1822 
1823 
1824 /*! \brief Returns the height of the text comprised between the two given lines.
1825 	\param startLine The index of the starting line.
1826 	\param endLine The index of the ending line.
1827 */
1828 float
1829 BTextView::TextHeight(int32 startLine, int32 endLine) const
1830 {
1831 	const int32 numLines = fLines->NumLines();
1832 	if (startLine < 0)
1833 		startLine = 0;
1834 	if (endLine > numLines - 1)
1835 		endLine = numLines - 1;
1836 
1837 	float height = (*fLines)[endLine + 1]->origin
1838 		- (*fLines)[startLine]->origin;
1839 
1840 	if (startLine != endLine && endLine == numLines - 1
1841 		&& fText->RealCharAt(fText->Length() - 1) == B_ENTER)
1842 		height += (*fLines)[endLine + 1]->origin - (*fLines)[endLine]->origin;
1843 
1844 	return ceilf(height);
1845 }
1846 
1847 
1848 void
1849 BTextView::GetTextRegion(int32 startOffset, int32 endOffset, BRegion *outRegion) const
1850 {
1851 	if (!outRegion)
1852 		return;
1853 
1854 	outRegion->MakeEmpty();
1855 
1856 	// return an empty region if the range is invalid
1857 	if (startOffset >= endOffset)
1858 		return;
1859 
1860 	float startLineHeight = 0.0;
1861 	float endLineHeight = 0.0;
1862 	BPoint startPt = PointAt(startOffset, &startLineHeight);
1863 	BPoint endPt = PointAt(endOffset, &endLineHeight);
1864 
1865 	startLineHeight = ceilf(startLineHeight);
1866 	endLineHeight = ceilf(endLineHeight);
1867 
1868 	BRect selRect;
1869 
1870 	if (startPt.y == endPt.y) {
1871 		// this is a one-line region
1872 		selRect.left = max_c(startPt.x, fTextRect.left);
1873 		selRect.top = startPt.y;
1874 		selRect.right = endPt.x - 1.0;
1875 		selRect.bottom = endPt.y + endLineHeight - 1.0;
1876 		outRegion->Include(selRect);
1877 	} else {
1878 		// more than one line in the specified offset range
1879 		selRect.left = max_c(startPt.x, fTextRect.left);
1880 		selRect.top = startPt.y;
1881 		selRect.right = fTextRect.right;
1882 		selRect.bottom = startPt.y + startLineHeight - 1.0;
1883 		outRegion->Include(selRect);
1884 
1885 		if (startPt.y + startLineHeight < endPt.y) {
1886 			// more than two lines in the range
1887 			selRect.left = fTextRect.left;
1888 			selRect.top = startPt.y + startLineHeight;
1889 			selRect.right = fTextRect.right;
1890 			selRect.bottom = endPt.y - 1.0;
1891 			outRegion->Include(selRect);
1892 		}
1893 
1894 		selRect.left = fTextRect.left;
1895 		selRect.top = endPt.y;
1896 		selRect.right = endPt.x - 1.0;
1897 		selRect.bottom = endPt.y + endLineHeight - 1.0;
1898 		outRegion->Include(selRect);
1899 	}
1900 }
1901 
1902 
1903 /*! \brief Scrolls the text so that the character at "inOffset" is within the visible range.
1904 	\param inOffset The offset of the character.
1905 */
1906 void
1907 BTextView::ScrollToOffset(int32 inOffset)
1908 {
1909 	_ScrollToOffset(inOffset, ScrollBar(B_HORIZONTAL) != NULL, ScrollBar(B_VERTICAL) != NULL);
1910 }
1911 
1912 
1913 void
1914 BTextView::_ScrollToOffset(int32 inOffset, bool useHorizontal, bool useVertical)
1915 {
1916 	BRect bounds = Bounds();
1917 	float lineHeight = 0.0;
1918 	float xDiff = 0.0;
1919 	float yDiff = 0.0;
1920         BPoint point = PointAt(inOffset, &lineHeight);
1921 
1922 	if (useHorizontal) {
1923 		if (point.x < bounds.left)
1924 			xDiff = point.x - bounds.left - bounds.IntegerWidth() / 2;
1925 		else if (point.x >= bounds.right)
1926 			xDiff = point.x - bounds.right + bounds.IntegerWidth() / 2;
1927 	}
1928 
1929 	if (useVertical) {
1930 		if (point.y < bounds.top)
1931 			yDiff = point.y - bounds.top - bounds.IntegerHeight() / 2;
1932 		else if (point.y >= bounds.bottom)
1933 			yDiff = point.y - bounds.bottom + bounds.IntegerHeight() / 2;
1934 	}
1935 
1936 	ScrollBy(xDiff, yDiff);
1937 }
1938 
1939 
1940 /*! \brief Scrolls the text so that the character which begins the current selection
1941 		is within the visible range.
1942 	\param inOffset The offset of the character.
1943 */
1944 void
1945 BTextView::ScrollToSelection()
1946 {
1947 	ScrollToOffset(fSelStart);
1948 }
1949 
1950 
1951 /*! \brief Highlight the text comprised between the given offset.
1952 	\param startOffset The offset of the text to highlight.
1953 	\param endOffset The offset where the text to highlight ends.
1954 */
1955 void
1956 BTextView::Highlight(int32 startOffset, int32 endOffset)
1957 {
1958 	// get real
1959 	if (startOffset >= endOffset)
1960 		return;
1961 
1962 	BRegion selRegion;
1963 	GetTextRegion(startOffset, endOffset, &selRegion);
1964 
1965 	SetDrawingMode(B_OP_INVERT);
1966 	FillRegion(&selRegion, B_SOLID_HIGH);
1967 	SetDrawingMode(B_OP_COPY);
1968 }
1969 
1970 
1971 /*! \brief Sets the BTextView's text rectangle to be the same as the passed rect.
1972 	\param rect A BRect.
1973 */
1974 void
1975 BTextView::SetTextRect(BRect rect)
1976 {
1977 	if (rect == fTextRect)
1978 		return;
1979 
1980 	fTextRect = rect;
1981 
1982 	if (Window() != NULL) {
1983 		Invalidate();
1984 		Window()->UpdateIfNeeded();
1985 	}
1986 }
1987 
1988 
1989 /*! \brief Returns the current BTextView's text rectangle.
1990 	\return The current text rectangle.
1991 */
1992 BRect
1993 BTextView::TextRect() const
1994 {
1995 	return fTextRect;
1996 }
1997 
1998 
1999 /*! \brief Sets whether the BTextView accepts multiple character styles.
2000 */
2001 void
2002 BTextView::SetStylable(bool stylable)
2003 {
2004 	fStylable = stylable;
2005 }
2006 
2007 
2008 /*! \brief Tells if the object is stylable.
2009 	\return true if the object is stylable, false otherwise.
2010 	If the object is stylable, it can show multiple fonts at the same time.
2011 */
2012 bool
2013 BTextView::IsStylable() const
2014 {
2015 	return fStylable;
2016 }
2017 
2018 
2019 /*! \brief Sets the distance between tab stops (in pixel).
2020 	\param width The distance (in pixel) between tab stops.
2021 */
2022 void
2023 BTextView::SetTabWidth(float width)
2024 {
2025 	if (width == fTabWidth)
2026 		return;
2027 
2028 	fTabWidth = width;
2029 
2030 	if (Window() != NULL)
2031 		_Refresh(0, fText->Length(), true, false);
2032 }
2033 
2034 
2035 /*! \brief Returns the BTextView's tab width.
2036 	\return The BTextView's tab width.
2037 */
2038 float
2039 BTextView::TabWidth() const
2040 {
2041 	return fTabWidth;
2042 }
2043 
2044 
2045 /*! \brief Makes the object selectable, or not selectable.
2046 	\param selectable If true, the object will be selectable from now on.
2047 	 if false, it won't be selectable anymore.
2048 */
2049 void
2050 BTextView::MakeSelectable(bool selectable)
2051 {
2052 	if (selectable == fSelectable)
2053 		return;
2054 
2055 	fSelectable = selectable;
2056 
2057 	if (Window() != NULL) {
2058 		if (fActive) {
2059 			// show/hide the caret, hilite/unhilite the selection
2060 			if (fSelStart != fSelEnd)
2061 				Highlight(fSelStart, fSelEnd);
2062 			else
2063 				_InvertCaret();
2064 		}
2065 	}
2066 }
2067 
2068 
2069 /*! \brief Tells if the object is selectable
2070 	\return \c true if the object is selectable,
2071 			\c false if not.
2072 */
2073 bool
2074 BTextView::IsSelectable() const
2075 {
2076 	return fSelectable;
2077 }
2078 
2079 
2080 /*! \brief Set (or remove) the editable property for the object.
2081 	\param editable If true, will make the object editable,
2082 		if false, will make it not editable.
2083 */
2084 void
2085 BTextView::MakeEditable(bool editable)
2086 {
2087 	if (editable == fEditable)
2088 		return;
2089 
2090 	fEditable = editable;
2091 	// TextControls change the color of the text when
2092 	// they are made editable, so we need to invalidate
2093 	// the NULL style here
2094 	// TODO: it works well, but it could be caused by a bug somewhere else
2095 	if (fEditable)
2096 		fStyles->InvalidateNullStyle();
2097 	if (Window() != NULL && fActive) {
2098 		if (!fEditable) {
2099 			_HideCaret();
2100 			_CancelInputMethod();
2101 		}
2102 	}
2103 }
2104 
2105 
2106 /*! \brief Tells if the object is editable.
2107 	\return \c true if the object is editable,
2108 			\c false if not.
2109 */
2110 bool
2111 BTextView::IsEditable() const
2112 {
2113 	return fEditable;
2114 }
2115 
2116 
2117 /*! \brief Set (or unset) word wrapping mode.
2118 	\param wrap Specifies if you want word wrapping active or not.
2119 */
2120 void
2121 BTextView::SetWordWrap(bool wrap)
2122 {
2123 	if (wrap == fWrap)
2124 		return;
2125 
2126 	if (Window() != NULL) {
2127 		if (fActive) {
2128 			// hide the caret, unhilite the selection
2129 			if (fSelStart != fSelEnd)
2130 				Highlight(fSelStart, fSelEnd);
2131 			else {
2132 				_HideCaret();
2133 			}
2134 		}
2135 
2136 		fWrap = wrap;
2137 		_Refresh(0, fText->Length(), true, true);
2138 
2139 		if (fActive) {
2140 			// show the caret, hilite the selection
2141 			if (fSelStart != fSelEnd && fSelectable)
2142 				Highlight(fSelStart, fSelEnd);
2143 			else
2144 				_ShowCaret();
2145 		}
2146 	}
2147 }
2148 
2149 
2150 /*! \brief Tells if word wrapping is activated.
2151 	\return true if word wrapping is active, false otherwise.
2152 */
2153 bool
2154 BTextView::DoesWordWrap() const
2155 {
2156 	return fWrap;
2157 }
2158 
2159 
2160 /*! \brief Sets the maximun number of bytes that the BTextView can contain.
2161 	\param max The new max number of bytes.
2162 */
2163 void
2164 BTextView::SetMaxBytes(int32 max)
2165 {
2166 	const int32 textLength = fText->Length();
2167 	fMaxBytes = max;
2168 
2169 	if (fMaxBytes < textLength) {
2170 		int32 offset = fMaxBytes;
2171 		// Delete the text after fMaxBytes, but
2172 		// respect multibyte characters boundaries.
2173 		const int32 previousInitial = _PreviousInitialByte(offset);
2174 		if (_NextInitialByte(previousInitial) != offset)
2175 			offset = previousInitial;
2176 
2177 		Delete(offset, textLength);
2178 	}
2179 }
2180 
2181 
2182 /*! \brief Returns the maximum number of bytes that the BTextView can contain.
2183 	\return the maximum number of bytes that the BTextView can contain.
2184 */
2185 int32
2186 BTextView::MaxBytes() const
2187 {
2188 	return fMaxBytes;
2189 }
2190 
2191 
2192 /*! \brief Adds the given char to the disallowed chars list.
2193 	\param aChar The character to add to the list.
2194 
2195 	After this function returns, the given character won't be accepted
2196 	by the textview anymore.
2197 */
2198 void
2199 BTextView::DisallowChar(uint32 aChar)
2200 {
2201 	if (fDisallowedChars == NULL)
2202 		fDisallowedChars = new BList;
2203 	if (!fDisallowedChars->HasItem(reinterpret_cast<void *>(aChar)))
2204 		fDisallowedChars->AddItem(reinterpret_cast<void *>(aChar));
2205 }
2206 
2207 
2208 /*! \brief Removes the given character from the disallowed list.
2209 	\param aChar The character to remove from the list.
2210 */
2211 void
2212 BTextView::AllowChar(uint32 aChar)
2213 {
2214 	if (fDisallowedChars != NULL)
2215 		fDisallowedChars->RemoveItem(reinterpret_cast<void *>(aChar));
2216 }
2217 
2218 
2219 /*! \brief Sets the way text is aligned within the text rectangle.
2220 	\param flag The new alignment.
2221 */
2222 void
2223 BTextView::SetAlignment(alignment flag)
2224 {
2225 	// Do a reality check
2226 	if (fAlignment != flag &&
2227 			(flag == B_ALIGN_LEFT ||
2228 			 flag == B_ALIGN_RIGHT ||
2229 			 flag == B_ALIGN_CENTER)) {
2230 		fAlignment = flag;
2231 
2232 		// After setting new alignment, update the view/window
2233 		BWindow *window = Window();
2234 		if (window) {
2235 			Invalidate();
2236 			window->UpdateIfNeeded();
2237 		}
2238 	}
2239 }
2240 
2241 
2242 /*! \brief Returns the current alignment of the text.
2243 	\return The current alignment.
2244 */
2245 alignment
2246 BTextView::Alignment() const
2247 {
2248 	return fAlignment;
2249 }
2250 
2251 
2252 /*! \brief Sets wheter a new line of text is automatically indented.
2253 	\param state The new autoindent state
2254 */
2255 void
2256 BTextView::SetAutoindent(bool state)
2257 {
2258 	fAutoindent = state;
2259 }
2260 
2261 
2262 /*! \brief Returns the current autoindent state.
2263 	\return The current autoindent state.
2264 */
2265 bool
2266 BTextView::DoesAutoindent() const
2267 {
2268 	return fAutoindent;
2269 }
2270 
2271 
2272 /*! \brief Set the color space for the offscreen BBitmap.
2273 	\param colors The new colorspace for the offscreen BBitmap.
2274 */
2275 void
2276 BTextView::SetColorSpace(color_space colors)
2277 {
2278 	if (colors != fColorSpace && fOffscreen) {
2279 		fColorSpace = colors;
2280 		_DeleteOffscreen();
2281 		_NewOffscreen();
2282 	}
2283 }
2284 
2285 
2286 /*! \brief Returns the colorspace of the offscreen BBitmap, if any.
2287 	\return The colorspace of the BTextView's offscreen BBitmap.
2288 */
2289 color_space
2290 BTextView::ColorSpace() const
2291 {
2292 	return fColorSpace;
2293 }
2294 
2295 
2296 /*! \brief Gives to the BTextView the ability to automatically resize itself when needed.
2297 	\param resize If true, the BTextView will automatically resize itself.
2298 	\param resizeView The BTextView's parent view, it's the view which resizes itself.
2299 	The resizing mechanism is alternative to the BView resizing. The container view
2300 	(the one passed to this function) should not automatically resize itself when the parent is
2301 	resized.
2302 */
2303 void
2304 BTextView::MakeResizable(bool resize, BView *resizeView)
2305 {
2306 	if (resize) {
2307 		fResizable = true;
2308 		fContainerView = resizeView;
2309 
2310 		// Wrapping mode and resizable mode can't live together
2311 		if (fWrap) {
2312 			fWrap = false;
2313 
2314 			if (fActive && Window() != NULL) {
2315 				if (fSelStart != fSelEnd && fSelectable)
2316 					Highlight(fSelStart, fSelEnd);
2317 				else
2318 					_HideCaret();
2319 			}
2320 		}
2321 	} else {
2322 		fResizable = false;
2323 		fContainerView = NULL;
2324 		if (fOffscreen)
2325 			_DeleteOffscreen();
2326 		_NewOffscreen();
2327 	}
2328 
2329 	_Refresh(0, fText->Length(), true, false);
2330 }
2331 
2332 
2333 /*! \brief Returns whether the BTextView is currently resizable.
2334 	\returns whether the BTextView is currently resizable.
2335 */
2336 bool
2337 BTextView::IsResizable() const
2338 {
2339 	return fResizable;
2340 }
2341 
2342 
2343 /*! \brief Enables or disables the undo mechanism.
2344 	\param undo If true enables the undo mechanism, if false, disables it.
2345 */
2346 void
2347 BTextView::SetDoesUndo(bool undo)
2348 {
2349 	if (undo && fUndo == NULL)
2350 		fUndo = new _BUndoBuffer_(this, B_UNDO_UNAVAILABLE);
2351 	else if (!undo && fUndo != NULL) {
2352 		delete fUndo;
2353 		fUndo = NULL;
2354 	}
2355 }
2356 
2357 
2358 /*! \brief Tells if the object is undoable.
2359 	\return Whether the object is undoable.
2360 */
2361 bool
2362 BTextView::DoesUndo() const
2363 {
2364 	return fUndo != NULL;
2365 }
2366 
2367 
2368 void
2369 BTextView::HideTyping(bool enabled)
2370 {
2371 	if (enabled)
2372 		Delete(0, fText->Length());
2373 
2374 	fText->SetPasswordMode(enabled);
2375 }
2376 
2377 
2378 bool
2379 BTextView::IsTypingHidden() const
2380 {
2381 	return fText->PasswordMode();
2382 }
2383 
2384 
2385 void
2386 BTextView::ResizeToPreferred()
2387 {
2388 	float widht, height;
2389 	GetPreferredSize(&widht, &height);
2390 	BView::ResizeTo(widht, height);
2391 }
2392 
2393 
2394 void
2395 BTextView::GetPreferredSize(float *width, float *height)
2396 {
2397 	BView::GetPreferredSize(width, height);
2398 }
2399 
2400 
2401 void
2402 BTextView::AllAttached()
2403 {
2404 	BView::AllAttached();
2405 }
2406 
2407 
2408 void
2409 BTextView::AllDetached()
2410 {
2411 	BView::AllDetached();
2412 }
2413 
2414 
2415 /* static */
2416 text_run_array *
2417 BTextView::AllocRunArray(int32 entryCount, int32 *outSize)
2418 {
2419 	int32 size = sizeof(text_run_array) + (entryCount - 1) * sizeof(text_run);
2420 
2421 	text_run_array *runArray = (text_run_array *)malloc(size);
2422 	if (runArray == NULL) {
2423 		if (outSize != NULL)
2424 			*outSize = 0;
2425 		return NULL;
2426 	}
2427 
2428 	memset(runArray, 0, sizeof(size));
2429 
2430 	runArray->count = entryCount;
2431 
2432 	// Call constructors explicitly as the text_run_array
2433 	// was allocated with malloc (and has to, for backwards
2434 	// compatibility)
2435 	for (int32 i = 0; i < runArray->count; i++) {
2436 		new (&runArray->runs[i].font) BFont;
2437 	}
2438 
2439 	if (outSize != NULL)
2440 		*outSize = size;
2441 
2442 	return runArray;
2443 }
2444 
2445 
2446 /* static */
2447 text_run_array *
2448 BTextView::CopyRunArray(const text_run_array *orig, int32 countDelta)
2449 {
2450 	text_run_array *copy = AllocRunArray(countDelta, NULL);
2451 	if (copy != NULL) {
2452 		for (int32 i = 0; i < countDelta; i++) {
2453 			copy->runs[i].offset = orig->runs[i].offset;
2454 			copy->runs[i].font = orig->runs[i].font;
2455 			copy->runs[i].color = orig->runs[i].color;
2456 		}
2457 	}
2458 	return copy;
2459 }
2460 
2461 
2462 /* static */
2463 void
2464 BTextView::FreeRunArray(text_run_array *array)
2465 {
2466 	if (array == NULL)
2467 		return;
2468 
2469 	// Call destructors explicitly
2470 	for (int32 i = 0; i < array->count; i++)
2471 		array->runs[i].font.~BFont();
2472 
2473 	free(array);
2474 }
2475 
2476 
2477 /* static */
2478 void *
2479 BTextView::FlattenRunArray(const text_run_array* runArray, int32* _size)
2480 {
2481 	CALLED();
2482 	int32 size = sizeof(flattened_text_run_array) + (runArray->count - 1)
2483 		* sizeof(flattened_text_run);
2484 
2485 	flattened_text_run_array *array = (flattened_text_run_array *)malloc(size);
2486 	if (array == NULL) {
2487 		if (_size)
2488 			*_size = 0;
2489 		return NULL;
2490 	}
2491 
2492 	array->magic = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayMagic);
2493 	array->version = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayVersion);
2494 	array->count = B_HOST_TO_BENDIAN_INT32(runArray->count);
2495 
2496 	for (int32 i = 0; i < runArray->count; i++) {
2497 		array->styles[i].offset = B_HOST_TO_BENDIAN_INT32(runArray->runs[i].offset);
2498 		runArray->runs[i].font.GetFamilyAndStyle(&array->styles[i].family,
2499 			&array->styles[i].style);
2500 		array->styles[i].size = B_HOST_TO_BENDIAN_FLOAT(runArray->runs[i].font.Size());
2501 		array->styles[i].shear = B_HOST_TO_BENDIAN_FLOAT(runArray->runs[i].font.Shear());
2502 		array->styles[i].face = B_HOST_TO_BENDIAN_INT16(runArray->runs[i].font.Face());
2503 		array->styles[i].red = runArray->runs[i].color.red;
2504 		array->styles[i].green = runArray->runs[i].color.green;
2505 		array->styles[i].blue = runArray->runs[i].color.blue;
2506 		array->styles[i].alpha = 255;
2507 		array->styles[i]._reserved_ = 0;
2508 	}
2509 
2510 	if (_size)
2511 		*_size = size;
2512 
2513 	return array;
2514 }
2515 
2516 
2517 /* static */
2518 text_run_array *
2519 BTextView::UnflattenRunArray(const void* data, int32* _size)
2520 {
2521 	CALLED();
2522 	flattened_text_run_array *array = (flattened_text_run_array *)data;
2523 
2524 	if (B_BENDIAN_TO_HOST_INT32(array->magic) != kFlattenedTextRunArrayMagic
2525 		|| B_BENDIAN_TO_HOST_INT32(array->version) != kFlattenedTextRunArrayVersion) {
2526 		if (_size)
2527 			*_size = 0;
2528 
2529 		return NULL;
2530 	}
2531 
2532 	int32 count = B_BENDIAN_TO_HOST_INT32(array->count);
2533 
2534 	text_run_array *runArray = AllocRunArray(count, _size);
2535 	if (runArray == NULL)
2536 		return NULL;
2537 
2538 	for (int32 i = 0; i < count; i++) {
2539 		runArray->runs[i].offset = B_BENDIAN_TO_HOST_INT32(array->styles[i].offset);
2540 
2541 		// Set family and style independently from each other, so that
2542 		// even if the family doesn't exist, we try to preserve the style
2543 		runArray->runs[i].font.SetFamilyAndStyle(array->styles[i].family, NULL);
2544 		runArray->runs[i].font.SetFamilyAndStyle(NULL, array->styles[i].style);
2545 
2546 		runArray->runs[i].font.SetSize(B_BENDIAN_TO_HOST_FLOAT(array->styles[i].size));
2547 		runArray->runs[i].font.SetShear(B_BENDIAN_TO_HOST_FLOAT(array->styles[i].shear));
2548 
2549 		uint16 face = B_BENDIAN_TO_HOST_INT16(array->styles[i].face);
2550 		if (face != B_REGULAR_FACE) {
2551 			// Be's version doesn't seem to set this correctly
2552 			runArray->runs[i].font.SetFace(face);
2553 		}
2554 
2555 		runArray->runs[i].color.red = array->styles[i].red;
2556 		runArray->runs[i].color.green = array->styles[i].green;
2557 		runArray->runs[i].color.blue = array->styles[i].blue;
2558 		runArray->runs[i].color.alpha = array->styles[i].alpha;
2559 	}
2560 
2561 	return runArray;
2562 }
2563 
2564 
2565 void
2566 BTextView::InsertText(const char *inText, int32 inLength, int32 inOffset,
2567 	const text_run_array *inRuns)
2568 {
2569 	CALLED();
2570 	// why add nothing?
2571 	if (inLength < 1)
2572 		return;
2573 
2574 	// TODO: Pin offset/lenght
2575 	// add the text to the buffer
2576 	fText->InsertText(inText, inLength, inOffset);
2577 
2578 	// update the start offsets of each line below offset
2579 	fLines->BumpOffset(inLength, LineAt(inOffset) + 1);
2580 
2581 	// update the style runs
2582 	fStyles->BumpOffset(inLength, fStyles->OffsetToRun(inOffset - 1) + 1);
2583 
2584 	if (inRuns != NULL) {
2585 		_SetRunArray(inOffset, inOffset + inLength, inRuns);
2586 	} else {
2587 		// apply nullStyle to inserted text
2588 		fStyles->SyncNullStyle(inOffset);
2589 		fStyles->SetStyleRange(inOffset, inOffset + inLength,
2590 			fText->Length(), B_FONT_ALL, NULL, NULL);
2591 	}
2592 }
2593 
2594 
2595 void
2596 BTextView::DeleteText(int32 fromOffset, int32 toOffset)
2597 {
2598 	CALLED();
2599 	// sanity checking
2600 	if (fromOffset >= toOffset || fromOffset < 0 || toOffset > fText->Length())
2601 		return;
2602 
2603 	// set nullStyle to style at beginning of range
2604 	fStyles->InvalidateNullStyle();
2605 	fStyles->SyncNullStyle(fromOffset);
2606 
2607 	// remove from the text buffer
2608 	fText->RemoveRange(fromOffset, toOffset);
2609 
2610 	// remove any lines that have been obliterated
2611 	fLines->RemoveLineRange(fromOffset, toOffset);
2612 
2613 	// remove any style runs that have been obliterated
2614 	fStyles->RemoveStyleRange(fromOffset, toOffset);
2615 }
2616 
2617 
2618 /*! \brief Undoes the last changes.
2619 	\param clipboard A clipboard to use for the undo operation.
2620 */
2621 void
2622 BTextView::Undo(BClipboard *clipboard)
2623 {
2624 	if (fUndo)
2625 		fUndo->Undo(clipboard);
2626 }
2627 
2628 
2629 undo_state
2630 BTextView::UndoState(bool *isRedo) const
2631 {
2632 	return fUndo == NULL ? B_UNDO_UNAVAILABLE : fUndo->State(isRedo);
2633 }
2634 
2635 
2636 void
2637 BTextView::GetDragParameters(BMessage *drag, BBitmap **bitmap, BPoint *point,
2638 	BHandler **handler)
2639 {
2640 	CALLED();
2641 	if (drag == NULL)
2642 		return;
2643 
2644 	// Add originator and action
2645 	drag->AddPointer("be:originator", this);
2646 	drag->AddInt32("be_actions", B_TRASH_TARGET);
2647 
2648 	// add the text
2649 	int32 numBytes = fSelEnd - fSelStart;
2650 	const char* text = fText->GetString(fSelStart, &numBytes);
2651 	drag->AddData("text/plain", B_MIME_TYPE, text, numBytes);
2652 
2653 	// add the corresponding styles
2654 	int32 size = 0;
2655 	text_run_array *styles = RunArray(fSelStart, fSelEnd, &size);
2656 
2657 	if (styles != NULL) {
2658 		drag->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
2659 			styles, size);
2660 
2661 		FreeRunArray(styles);
2662 	}
2663 
2664 	if (bitmap != NULL)
2665 		*bitmap = NULL;
2666 	if (handler != NULL)
2667 		*handler = NULL;
2668 }
2669 
2670 
2671 void BTextView::_ReservedTextView3() {}
2672 void BTextView::_ReservedTextView4() {}
2673 void BTextView::_ReservedTextView5() {}
2674 void BTextView::_ReservedTextView6() {}
2675 void BTextView::_ReservedTextView7() {}
2676 void BTextView::_ReservedTextView8() {}
2677 void BTextView::_ReservedTextView9() {}
2678 void BTextView::_ReservedTextView10() {}
2679 void BTextView::_ReservedTextView11() {}
2680 void BTextView::_ReservedTextView12() {}
2681 
2682 
2683 /*! \brief Inits the BTextView object.
2684 	\param textRect The BTextView's text rect.
2685 	\param initialFont The font which the BTextView will use.
2686 	\param initialColor The initial color of the text.
2687 */
2688 void
2689 BTextView::_InitObject(BRect textRect, const BFont *initialFont,
2690 						   const rgb_color *initialColor)
2691 {
2692 	BFont font;
2693 	if (initialFont == NULL)
2694 		GetFont(&font);
2695 	else
2696 		font = *initialFont;
2697 
2698 	_NormalizeFont(&font);
2699 
2700 	if (initialColor == NULL)
2701 		initialColor = &kBlackColor;
2702 
2703 	fText = new _BTextGapBuffer_;
2704 	fLines = new _BLineBuffer_;
2705 	fStyles = new _BStyleBuffer_(&font, initialColor);
2706 
2707 	// We put these here instead of in the constructor initializer list
2708 	// to have less code duplication, and a single place where to do changes
2709 	// if needed.,
2710 	fTextRect = textRect;
2711 	fSelStart = fSelEnd = 0;
2712 	fCaretVisible = false;
2713 	fCaretTime = 0;
2714 	fClickOffset = 0;
2715 	fClickCount = 0;
2716 	fClickTime = 0;
2717 	fDragOffset = -1;
2718 	fCursor = 0;
2719 	fActive = false;
2720 	fStylable = false;
2721 	fTabWidth = 28.0;
2722 	fSelectable = true;
2723 	fEditable = true;
2724 	fWrap = true;
2725 	fMaxBytes = LONG_MAX;
2726 	fDisallowedChars = NULL;
2727 	fAlignment = B_ALIGN_LEFT;
2728 	fAutoindent = false;
2729 	fOffscreen = NULL;
2730 	fColorSpace = B_CMAP8;
2731 	fResizable = false;
2732 	fContainerView = NULL;
2733 	fUndo = NULL;
2734 	fInline = NULL;
2735 	fDragRunner = NULL;
2736 	fClickRunner = NULL;
2737 	fTrackingMouse = NULL;
2738 	fTextChange = NULL;
2739 }
2740 
2741 
2742 /*! \brief Called when Backspace key is pressed.
2743 */
2744 void
2745 BTextView::_HandleBackspace()
2746 {
2747 	if (fUndo) {
2748 		_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(
2749 			fUndo);
2750 		if (!undoBuffer) {
2751 			delete fUndo;
2752 			fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
2753 		}
2754 		undoBuffer->BackwardErase();
2755 	}
2756 
2757 	if (fSelStart == fSelEnd) {
2758 		if (fSelStart == 0)
2759 			return;
2760 		else
2761 			fSelStart = _PreviousInitialByte(fSelStart);
2762 	} else
2763 		Highlight(fSelStart, fSelEnd);
2764 
2765 	DeleteText(fSelStart, fSelEnd);
2766 	fClickOffset = fSelEnd = fSelStart;
2767 
2768 	_Refresh(fSelStart, fSelEnd, true, true);
2769 }
2770 
2771 
2772 /*! \brief Called when any arrow key is pressed.
2773 	\param inArrowKey The code for the pressed key.
2774 */
2775 void
2776 BTextView::_HandleArrowKey(uint32 inArrowKey)
2777 {
2778 	// return if there's nowhere to go
2779 	if (fText->Length() == 0)
2780 		return;
2781 
2782 	int32 selStart = fSelStart;
2783 	int32 selEnd = fSelEnd;
2784 
2785 	int32 modifiers = 0;
2786 	BMessage *message = Window()->CurrentMessage();
2787 	if (message != NULL)
2788 		message->FindInt32("modifiers", &modifiers);
2789 
2790 	bool shiftDown = modifiers & B_SHIFT_KEY;
2791 
2792 	int32 currentOffset = fClickOffset;
2793 	switch (inArrowKey) {
2794 		case B_LEFT_ARROW:
2795 			if (shiftDown) {
2796 				fClickOffset = _PreviousInitialByte(fClickOffset);
2797 				if (fClickOffset != currentOffset) {
2798 					if (fClickOffset >= fSelStart)
2799 						selEnd = fClickOffset;
2800 					else
2801 						selStart = fClickOffset;
2802 				}
2803 			} else if (fSelStart != fSelEnd)
2804 				fClickOffset = fSelStart;
2805 			else
2806 				fClickOffset = _PreviousInitialByte(fSelStart);
2807 
2808 			break;
2809 
2810 		case B_RIGHT_ARROW:
2811 			if (shiftDown) {
2812 				fClickOffset = _NextInitialByte(fClickOffset);
2813 				if (fClickOffset != currentOffset) {
2814 					if (fClickOffset <= fSelEnd)
2815 						selStart = fClickOffset;
2816 					else
2817 						selEnd = fClickOffset;
2818 				}
2819 			} else if (fSelStart != fSelEnd)
2820 				fClickOffset = fSelEnd;
2821 			else
2822 				fClickOffset = _NextInitialByte(fSelEnd);
2823 			break;
2824 
2825 		case B_UP_ARROW:
2826 		{
2827 			float height;
2828 			BPoint point = PointAt(fClickOffset, &height);
2829 			point.y -= height;
2830 			fClickOffset = OffsetAt(point);
2831 			if (shiftDown) {
2832 				if (fClickOffset != currentOffset) {
2833 					if (fClickOffset >= fSelStart)
2834 						selEnd = fClickOffset;
2835 					else
2836 						selStart = fClickOffset;
2837 				}
2838 			}
2839 			break;
2840 		}
2841 
2842 		case B_DOWN_ARROW:
2843 		{
2844 			float height;
2845 			BPoint point = PointAt(fClickOffset, &height);
2846 			point.y += height;
2847 			fClickOffset = OffsetAt(point);
2848 			if (shiftDown) {
2849 				if (fClickOffset != currentOffset) {
2850 					if (fClickOffset <= fSelEnd)
2851 						selStart = fClickOffset;
2852 					else
2853 						selEnd = fClickOffset;
2854 				}
2855 			}
2856 			break;
2857 		}
2858 	}
2859 
2860 	// invalidate the null style
2861 	fStyles->InvalidateNullStyle();
2862 
2863 	currentOffset = fClickOffset;
2864 	if (shiftDown)
2865 		Select(selStart, selEnd);
2866 	else
2867 		Select(fClickOffset, fClickOffset);
2868 
2869 	fClickOffset = currentOffset;
2870 		// Select sets fClickOffset = fSelEnd
2871 
2872 	// scroll if needed
2873 	ScrollToOffset(fClickOffset);
2874 }
2875 
2876 
2877 /*! \brief Called when the Delete key is pressed.
2878 */
2879 void
2880 BTextView::_HandleDelete()
2881 {
2882 	if (fUndo) {
2883 		_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(
2884 			fUndo);
2885 		if (!undoBuffer) {
2886 			delete fUndo;
2887 			fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
2888 		}
2889 		undoBuffer->ForwardErase();
2890 	}
2891 
2892 	if (fSelStart == fSelEnd) {
2893 		if (fSelEnd == fText->Length())
2894 			return;
2895 		else
2896 			fSelEnd = _NextInitialByte(fSelEnd);
2897 	} else
2898 		Highlight(fSelStart, fSelEnd);
2899 
2900 	DeleteText(fSelStart, fSelEnd);
2901 
2902 	fClickOffset = fSelEnd = fSelStart;
2903 
2904 	_Refresh(fSelStart, fSelEnd, true, true);
2905 }
2906 
2907 
2908 /*! \brief Called when a "Page key" is pressed.
2909 	\param inPageKey The page key which has been pressed.
2910 */
2911 void
2912 BTextView::_HandlePageKey(uint32 inPageKey)
2913 {
2914 	int32 mods = 0;
2915 	BMessage *currentMessage = Window()->CurrentMessage();
2916 	if (currentMessage)
2917 		currentMessage->FindInt32("modifiers", &mods);
2918 
2919 	bool shiftDown = mods & B_SHIFT_KEY;
2920 	STELine* line = NULL;
2921 	int32 start = fSelStart, end = fSelEnd;
2922 
2923 	switch (inPageKey) {
2924 		case B_HOME:
2925 			line = (*fLines)[CurrentLine()];
2926 			fClickOffset = line->offset;
2927 			if (shiftDown) {
2928 				if (fClickOffset <= fSelStart) {
2929 					start = fClickOffset;
2930 					end = fSelEnd;
2931 				} else {
2932 					start = fSelStart;
2933 					end = fClickOffset;
2934 				}
2935 			} else
2936 				start = end = fClickOffset;
2937 
2938 			break;
2939 
2940 		case B_END:
2941 			// If we are on the last line, just go to the last
2942 			// character in the buffer, otherwise get the starting
2943 			// offset of the next line, and go to the previous character
2944 			if (CurrentLine() + 1 < fLines->NumLines()) {
2945 				line = (*fLines)[CurrentLine() + 1];
2946 				fClickOffset = _PreviousInitialByte(line->offset);
2947 			} else {
2948 				// This check if needed to avoid moving the cursor
2949 				// when the cursor is on the last line, and that line
2950 				// is empty
2951 				if (fClickOffset != fText->Length()) {
2952 					fClickOffset = fText->Length();
2953 					if (ByteAt(fClickOffset - 1) == B_ENTER)
2954 						fClickOffset--;
2955 				}
2956 			}
2957 
2958 			if (shiftDown) {
2959 				if (fClickOffset >= fSelEnd) {
2960 					start = fSelStart;
2961 					end = fClickOffset;
2962 				} else {
2963 					start = fClickOffset;
2964 					end = fSelEnd;
2965 				}
2966 			} else
2967 				start = end = fClickOffset;
2968 
2969 			break;
2970 
2971 		case B_PAGE_UP:
2972 		{
2973 			BPoint currentPos = PointAt(fClickOffset);
2974 
2975 			currentPos.y -= Bounds().Height();
2976 			fClickOffset = OffsetAt(LineAt(currentPos));
2977 
2978 			if (shiftDown) {
2979 				if (fClickOffset <= fSelStart) {
2980 					start = fClickOffset;
2981 					end = fSelEnd;
2982 				} else {
2983 					start = fSelStart;
2984 					end = fClickOffset;
2985 				}
2986 			} else
2987 				start = end = fClickOffset;
2988 			break;
2989 		}
2990 
2991 		case B_PAGE_DOWN:
2992 		{
2993 			BPoint currentPos = PointAt(fClickOffset);
2994 
2995 			currentPos.y += Bounds().Height();
2996 			fClickOffset = OffsetAt(LineAt(currentPos) + 1);
2997 
2998 			if (shiftDown) {
2999 				if (fClickOffset >= fSelEnd) {
3000 					start = fSelStart;
3001 					end = fClickOffset;
3002 				} else {
3003 					start = fClickOffset;
3004 					end = fSelEnd;
3005 				}
3006 			} else
3007 				start = end = fClickOffset;
3008 
3009 			break;
3010 		}
3011 	}
3012 
3013 	ScrollToOffset(fClickOffset);
3014 	Select(start, end);
3015 }
3016 
3017 
3018 /*! \brief Called when an alphanumeric key is pressed.
3019 	\param bytes The string or character associated with the key.
3020 	\param numBytes The amount of bytes containes in "bytes".
3021 */
3022 void
3023 BTextView::_HandleAlphaKey(const char *bytes, int32 numBytes)
3024 {
3025 	// TODO: block input if not editable (Andrew)
3026 	if (fUndo) {
3027 		_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo);
3028 		if (!undoBuffer) {
3029 			delete fUndo;
3030 			fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
3031 		}
3032 		undoBuffer->InputCharacter(numBytes);
3033 	}
3034 
3035 	bool erase = fSelStart != fText->Length();
3036 
3037 	if (fSelStart != fSelEnd) {
3038 		Highlight(fSelStart, fSelEnd);
3039 		DeleteText(fSelStart, fSelEnd);
3040 		erase = true;
3041 	}
3042 
3043 	if (fAutoindent && numBytes == 1 && *bytes == B_ENTER) {
3044 		int32 start, offset;
3045 		start = offset = OffsetAt(LineAt(fSelStart));
3046 
3047 		while (ByteAt(offset) != '\0' &&
3048 				(ByteAt(offset) == B_TAB || ByteAt(offset) == B_SPACE))
3049 			offset++;
3050 
3051 		if (start != offset)
3052 			_DoInsertText(Text() + start, offset - start, fSelStart, NULL, NULL);
3053 
3054 		_DoInsertText(bytes, numBytes, fSelStart, NULL, NULL);
3055 
3056 	} else
3057 		_DoInsertText(bytes, numBytes, fSelStart, NULL, NULL);
3058 
3059 	fClickOffset = fSelEnd;
3060 
3061 	ScrollToOffset(fClickOffset);
3062 }
3063 
3064 
3065 /*! \brief Redraw the text comprised between the two given offsets,
3066 	recalculating linebreaks if needed.
3067 	\param fromOffset The offset from where to refresh.
3068 	\param toOffset The offset where to refresh to.
3069 	\param erase If true, the function will also erase the textview content
3070 	in the parts where text isn't present.
3071 	\param scroll If true, function will scroll the view to the end offset.
3072 */
3073 void
3074 BTextView::_Refresh(int32 fromOffset, int32 toOffset, bool erase, bool scroll)
3075 {
3076 	// TODO: Cleanup
3077 	float saveHeight = fTextRect.Height();
3078 	int32 fromLine = LineAt(fromOffset);
3079 	int32 toLine = LineAt(toOffset);
3080 	int32 saveFromLine = fromLine;
3081 	int32 saveToLine = toLine;
3082 	float saveLineHeight = LineHeight(fromLine);
3083 
3084 	_RecalculateLineBreaks(&fromLine, &toLine);
3085 
3086 	// TODO: Maybe there is still something we can do without a window...
3087 	if (!Window())
3088 		return;
3089 
3090 	BRect bounds = Bounds();
3091 	float newHeight = fTextRect.Height();
3092 
3093 	// if the line breaks have changed, force an erase
3094 	if (fromLine != saveFromLine || toLine != saveToLine
3095 			|| newHeight != saveHeight ) {
3096 		erase = true;
3097 		fromOffset = -1;
3098 	}
3099 
3100 	if (newHeight != saveHeight) {
3101 		// the text area has changed
3102 		if (newHeight < saveHeight)
3103 			toLine = LineAt(BPoint(0.0f, saveHeight + fTextRect.top));
3104 		else
3105 			toLine = LineAt(BPoint(0.0f, newHeight + fTextRect.top));
3106 	}
3107 
3108 	// draw only those lines that are visible
3109 	int32 fromVisible = LineAt(BPoint(0.0f, bounds.top));
3110 	int32 toVisible = LineAt(BPoint(0.0f, bounds.bottom));
3111 	fromLine = max_c(fromVisible, fromLine);
3112 	toLine = min_c(toLine, toVisible);
3113 
3114 	int32 drawOffset = fromOffset;
3115 	if (LineHeight(fromLine) != saveLineHeight
3116 		|| newHeight < saveHeight || fromLine < saveFromLine
3117 		|| fAlignment != B_ALIGN_LEFT)
3118 		drawOffset = (*fLines)[fromLine]->offset;
3119 
3120 	if (fResizable)
3121 		_AutoResize(false);
3122 
3123 	_DrawLines(fromLine, toLine, drawOffset, erase);
3124 
3125 	// erase the area below the text
3126 	BRect eraseRect = bounds;
3127 	eraseRect.top = fTextRect.top + (*fLines)[fLines->NumLines()]->origin;
3128 	eraseRect.bottom = fTextRect.top + saveHeight;
3129 	if (eraseRect.bottom > eraseRect.top && eraseRect.Intersects(bounds)) {
3130 		SetLowColor(ViewColor());
3131 		FillRect(eraseRect, B_SOLID_LOW);
3132 	}
3133 
3134 	// update the scroll bars if the text area has changed
3135 	if (newHeight != saveHeight)
3136 		_UpdateScrollbars();
3137 
3138 	if (scroll)
3139 		ScrollToSelection();
3140 
3141 	Flush();
3142 }
3143 
3144 
3145 void
3146 BTextView::_RecalculateLineBreaks(int32 *startLine, int32 *endLine)
3147 {
3148 	// are we insane?
3149 	*startLine = (*startLine < 0) ? 0 : *startLine;
3150 	*endLine = (*endLine > fLines->NumLines() - 1) ? fLines->NumLines() - 1 : *endLine;
3151 
3152 	int32 textLength = fText->Length();
3153 	int32 lineIndex = (*startLine > 0) ? *startLine - 1 : 0;
3154 	int32 recalThreshold = (*fLines)[*endLine + 1]->offset;
3155 	float width = fTextRect.Width();
3156 	STELine* curLine = (*fLines)[lineIndex];
3157 	STELine* nextLine = curLine + 1;
3158 
3159 	do {
3160 		float ascent, descent;
3161 		int32 fromOffset = curLine->offset;
3162 		int32 toOffset = _FindLineBreak(fromOffset, &ascent, &descent, &width);
3163 
3164 		// we want to advance at least by one character
3165 		int32 nextOffset = _NextInitialByte(fromOffset);
3166 		if (toOffset < nextOffset && fromOffset < textLength)
3167 			toOffset = nextOffset;
3168 
3169 		// set the ascent of this line
3170 		curLine->ascent = ascent;
3171 
3172 		lineIndex++;
3173 		STELine saveLine = *nextLine;
3174 		if (lineIndex > fLines->NumLines() || toOffset < nextLine->offset) {
3175 			// the new line comes before the old line start, add a line
3176 			STELine newLine;
3177 			newLine.offset = toOffset;
3178 			newLine.origin = ceilf(curLine->origin + ascent + descent) + 1;
3179 			newLine.ascent = 0;
3180 			fLines->InsertLine(&newLine, lineIndex);
3181 		} else {
3182 			// update the exising line
3183 			nextLine->offset = toOffset;
3184 			nextLine->origin = ceilf(curLine->origin + ascent + descent) + 1;
3185 
3186 			// remove any lines that start before the current line
3187 			while (lineIndex < fLines->NumLines()
3188 				&& toOffset >= ((*fLines)[lineIndex] + 1)->offset) {
3189 				fLines->RemoveLines(lineIndex + 1);
3190 			}
3191 
3192 			nextLine = (*fLines)[lineIndex];
3193 			if (nextLine->offset == saveLine.offset) {
3194 				if (nextLine->offset >= recalThreshold) {
3195 					if (nextLine->origin != saveLine.origin)
3196 						fLines->BumpOrigin(nextLine->origin - saveLine.origin,
3197 							lineIndex + 1);
3198 					break;
3199 				}
3200 			} else {
3201 				if (lineIndex > 0 && lineIndex == *startLine)
3202 					*startLine = lineIndex - 1;
3203 			}
3204 		}
3205 
3206 		curLine = (*fLines)[lineIndex];
3207 		nextLine = curLine + 1;
3208 	} while (curLine->offset < textLength);
3209 
3210 	// update the text rect
3211 	float newHeight = TextHeight(0, fLines->NumLines() - 1);
3212 	fTextRect.bottom = fTextRect.top + newHeight;
3213 
3214 	*endLine = lineIndex - 1;
3215 	*startLine = min_c(*startLine, *endLine);
3216 }
3217 
3218 
3219 int32
3220 BTextView::_FindLineBreak(int32 fromOffset, float *outAscent, float *outDescent,
3221 	float *ioWidth)
3222 {
3223 	*outAscent = 0.0;
3224 	*outDescent = 0.0;
3225 
3226 	const int32 limit = fText->Length();
3227 
3228 	// is fromOffset at the end?
3229 	if (fromOffset >= limit) {
3230 		// try to return valid height info anyway
3231 		if (fStyles->NumRuns() > 0) {
3232 			fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, outAscent,
3233 				outDescent);
3234 		} else {
3235 			if (fStyles->IsValidNullStyle()) {
3236 				const BFont *font = NULL;
3237 				fStyles->GetNullStyle(&font, NULL);
3238 
3239 				font_height fh;
3240 				font->GetHeight(&fh);
3241 				*outAscent = fh.ascent;
3242 				*outDescent = fh.descent + fh.leading;
3243 			}
3244 		}
3245 
3246 		return limit;
3247 	}
3248 
3249 	int32 offset = fromOffset;
3250 
3251 	// Text wrapping is turned off.
3252 	// Just find the offset of the first \n character
3253 	if (!fWrap) {
3254 		offset = limit - fromOffset;
3255 		fText->FindChar(B_ENTER, fromOffset, &offset);
3256 		offset += fromOffset;
3257 		offset = (offset < limit) ? offset + 1 : limit;
3258 
3259 		*ioWidth = _StyledWidth(fromOffset, offset - fromOffset, outAscent, outDescent);
3260 
3261 		return offset;
3262 	}
3263 
3264 	bool done = false;
3265 	float ascent = 0.0;
3266 	float descent = 0.0;
3267 	int32 delta = 0;
3268 	float deltaWidth = 0.0;
3269 	float tabWidth = 0.0;
3270 	float strWidth = 0.0;
3271 
3272 	// wrap the text
3273 	do {
3274 		bool foundTab = false;
3275 
3276 		// find the next line break candidate
3277 		for ( ; (offset + delta) < limit ; delta++) {
3278 			if (CanEndLine(offset + delta))
3279 				break;
3280 		}
3281 		for ( ; (offset + delta) < limit; delta++) {
3282 			uchar theChar = fText->RealCharAt(offset + delta);
3283 			if (!CanEndLine(offset + delta))
3284 				break;
3285 
3286 			if (theChar == B_ENTER) {
3287 				// found a newline, we're done!
3288 				done = true;
3289 				delta++;
3290 				break;
3291 			} else {
3292 				// include all trailing spaces and tabs,
3293 				// but not spaces after tabs
3294 				if (theChar != B_SPACE && theChar != B_TAB)
3295 					break;
3296 				else {
3297 					if (theChar == B_SPACE && foundTab)
3298 						break;
3299 					else {
3300 						if (theChar == B_TAB)
3301 							foundTab = true;
3302 					}
3303 				}
3304 			}
3305 		}
3306 		delta = max_c(delta, 1);
3307 
3308 		deltaWidth = _StyledWidth(offset, delta, &ascent, &descent);
3309 		strWidth += deltaWidth;
3310 
3311 		if (!foundTab)
3312 			tabWidth = 0.0;
3313 		else {
3314 			int32 tabCount = 0;
3315 			for (int32 i = delta - 1; fText->RealCharAt(offset + i) == B_TAB;
3316 					i--) {
3317 				tabCount++;
3318 			}
3319 
3320 			tabWidth = _ActualTabWidth(strWidth);
3321 			if (tabCount > 1)
3322 				tabWidth += ((tabCount - 1) * fTabWidth);
3323 			strWidth += tabWidth;
3324 		}
3325 
3326 		if (strWidth >= *ioWidth) {
3327 			// we've found where the line will wrap
3328 			bool foundNewline = done;
3329 			done = true;
3330 			int32 pos = delta - 1;
3331 			if (!CanEndLine(offset + pos))
3332 				break;
3333 
3334 			strWidth -= (deltaWidth + tabWidth);
3335 
3336 			while (offset + pos > offset) {
3337 				if (!CanEndLine(offset + pos))
3338 					break;
3339 
3340 				pos--;
3341 			}
3342 
3343 			strWidth += _StyledWidth(offset, pos + 1, &ascent, &descent);
3344 			if (strWidth >= *ioWidth)
3345 				break;
3346 
3347 			if (!foundNewline) {
3348 				while (offset + delta < limit) {
3349 					const char realChar = fText->RealCharAt(offset + delta);
3350 					if (realChar != B_SPACE && realChar != B_TAB)
3351 						break;
3352 
3353 					delta++;
3354 				}
3355 				if (offset + delta < limit
3356 					&& fText->RealCharAt(offset + delta) == B_ENTER)
3357 					delta++;
3358 			}
3359 			// get the ascent and descent of the spaces/tabs
3360 			_StyledWidth(offset, delta, &ascent, &descent);
3361 		}
3362 
3363 		*outAscent = max_c(ascent, *outAscent);
3364 		*outDescent = max_c(descent, *outDescent);
3365 
3366 		offset += delta;
3367 		delta = 0;
3368 	} while (offset < limit && !done);
3369 
3370 	if (offset - fromOffset < 1) {
3371 		// there weren't any words that fit entirely in this line
3372 		// force a break in the middle of a word
3373 		*outAscent = 0.0;
3374 		*outDescent = 0.0;
3375 		strWidth = 0.0;
3376 
3377 		int32 current = fromOffset;
3378 		for (offset = fromOffset; offset <= limit; current = offset,
3379 				offset = _NextInitialByte(offset)) {
3380 			strWidth += _StyledWidth(current, offset - current, &ascent,
3381 				&descent);
3382 			if (strWidth >= *ioWidth) {
3383 				offset = _PreviousInitialByte(offset);
3384 				break;
3385 			}
3386 
3387 			*outAscent = max_c(ascent, *outAscent);
3388 			*outDescent = max_c(descent, *outDescent);
3389 		}
3390 	}
3391 
3392 	return min_c(offset, limit);
3393 }
3394 
3395 
3396 /*! \brief Calculate the width of the text within the given limits.
3397 	\param fromOffset The offset where to start.
3398 	\param length The length of the text to examine.
3399 	\param outAscent A pointer to a float which will contain the maximum ascent.
3400 	\param outDescent A pointer to a float which will contain the maximum descent.
3401 	\return The width for the text within the given limits.
3402 */
3403 float
3404 BTextView::_StyledWidth(int32 fromOffset, int32 length, float *outAscent,
3405 	float *outDescent) const
3406 {
3407 	float result = 0.0;
3408 	float ascent = 0.0;
3409 	float descent = 0.0;
3410 	float maxAscent = 0.0;
3411 	float maxDescent = 0.0;
3412 
3413 	// iterate through the style runs
3414 	const BFont *font = NULL;
3415 	int32 numChars;
3416 	while ((numChars = fStyles->Iterate(fromOffset, length, fInline, &font,
3417 			NULL, &ascent, &descent)) != 0) {
3418 		maxAscent = max_c(ascent, maxAscent);
3419 		maxDescent = max_c(descent, maxDescent);
3420 
3421 #if USE_WIDTHBUFFER
3422 		// Use _BWidthBuffer_ if possible
3423 		if (sWidths != NULL) {
3424 			LockWidthBuffer();
3425 			result += sWidths->StringWidth(*fText, fromOffset, numChars, font);
3426 			UnlockWidthBuffer();
3427 		} else {
3428 #endif
3429 			const char* text = fText->GetString(fromOffset, &numChars);
3430 			result += font->StringWidth(text, numChars);
3431 
3432 #if USE_WIDTHBUFFER
3433 		}
3434 #endif
3435 
3436 		fromOffset += numChars;
3437 		length -= numChars;
3438 	}
3439 
3440 	if (outAscent != NULL)
3441 		*outAscent = maxAscent;
3442 	if (outDescent != NULL)
3443 		*outDescent = maxDescent;
3444 
3445 	return result;
3446 }
3447 
3448 
3449 // Unlike the _StyledWidth method, this one takes as parameter
3450 // the number of chars, not the number of bytes.
3451 float
3452 BTextView::_StyledWidthUTF8Safe(int32 fromOffset, int32 numChars,
3453 	float *outAscent, float *outDescent) const
3454 {
3455 	int32 toOffset = fromOffset;
3456 	while (numChars--)
3457 		toOffset = _NextInitialByte(toOffset);
3458 
3459 	const int32 length = toOffset - fromOffset;
3460 	return _StyledWidth(fromOffset, length, outAscent, outDescent);
3461 }
3462 
3463 
3464 /*! \brief Calculate the actual tab width for the given location.
3465 	\param location The location to calculate the tab width of.
3466 	\return The actual tab width for the given location
3467 */
3468 float
3469 BTextView::_ActualTabWidth(float location) const
3470 {
3471 	return fTabWidth - fmod(location, fTabWidth);
3472 }
3473 
3474 
3475 void
3476 BTextView::_DoInsertText(const char *inText, int32 inLength, int32 inOffset,
3477 	const text_run_array *inRuns, _BTextChangeResult_ *outResult)
3478 {
3479 	_CancelInputMethod();
3480 
3481 	if (TextLength() + inLength > MaxBytes())
3482 		return;
3483 
3484 	if (fSelStart != fSelEnd)
3485 		Select(fSelStart, fSelStart);
3486 
3487 	// Don't do any check, the public methods will have adjusted
3488 	// eventual bogus values...
3489 
3490 	const int32 textLength = TextLength();
3491 	if (inOffset > textLength)
3492 		inOffset = textLength;
3493 
3494 	// copy data into buffer
3495 	InsertText(inText, inLength, inOffset, inRuns);
3496 
3497 	// offset the caret/selection
3498 	//int32 saveStart = fSelStart;
3499 	fSelStart += inLength;
3500 	fSelEnd += inLength;
3501 
3502 	// recalc line breaks and draw the text
3503 	_Refresh(inOffset, textLength, true, false);
3504 }
3505 
3506 
3507 void
3508 BTextView::_DoDeleteText(int32 fromOffset, int32 toOffset, _BTextChangeResult_ *outResult)
3509 {
3510 	CALLED();
3511 }
3512 
3513 
3514 void
3515 BTextView::_DrawLine(BView *view, const int32 &lineNum, const int32 &startOffset,
3516 			const bool &erase, BRect &eraseRect, BRegion &inputRegion)
3517 {
3518 	STELine *line = (*fLines)[lineNum];
3519 	float startLeft = fTextRect.left;
3520 	if (startOffset != -1) {
3521 		if (ByteAt(startOffset) == B_ENTER) {
3522 			// StartOffset is a newline
3523 			startLeft = PointAt(line->offset).x;
3524 		} else
3525 			startLeft = PointAt(startOffset).x;
3526 	}
3527 
3528 	int32 length = (line + 1)->offset;
3529 	if (startOffset != -1)
3530 		length -= startOffset;
3531 	else
3532 		length -= line->offset;
3533 
3534 	// DrawString() chokes if you draw a newline
3535 	if (ByteAt((line + 1)->offset - 1) == B_ENTER)
3536 		length--;
3537 	if (fAlignment != B_ALIGN_LEFT) {
3538 		// B_ALIGN_RIGHT
3539 		startLeft = (fTextRect.right - LineWidth(lineNum));
3540 		if (fAlignment == B_ALIGN_CENTER)
3541 			startLeft /= 2;
3542 		startLeft += fTextRect.left;
3543 	}
3544 
3545 	view->MovePenTo(startLeft, line->origin + line->ascent + fTextRect.top + 1);
3546 
3547 	if (erase) {
3548 		eraseRect.top = line->origin + fTextRect.top;
3549 		eraseRect.bottom = (line + 1)->origin + fTextRect.top;
3550 
3551 		view->FillRect(eraseRect, B_SOLID_LOW);
3552 	}
3553 
3554 	// do we have any text to draw?
3555 	if (length <= 0)
3556 		return;
3557 
3558 	bool foundTab = false;
3559 	int32 tabChars = 0;
3560 	int32 numTabs = 0;
3561 	int32 offset = startOffset != -1 ? startOffset : line->offset;
3562 	const BFont *font = NULL;
3563 	const rgb_color *color = NULL;
3564 	int32 numBytes;
3565 	// iterate through each style on this line
3566 	while ((numBytes = fStyles->Iterate(offset, length, fInline, &font,
3567 			&color)) != 0) {
3568 		view->SetFont(font);
3569 		view->SetHighColor(*color);
3570 
3571 		tabChars = numBytes;
3572 		do {
3573 			foundTab = fText->FindChar(B_TAB, offset, &tabChars);
3574 			if (foundTab) {
3575 				do {
3576 					numTabs++;
3577 					if (ByteAt(offset + tabChars + numTabs) != B_TAB)
3578 						break;
3579 				} while ((tabChars + numTabs) < numBytes);
3580 			}
3581 
3582 			if (inputRegion.CountRects() > 0) {
3583 				BRegion textRegion;
3584 				GetTextRegion(offset, offset + length, &textRegion);
3585 
3586 				textRegion.IntersectWith(&inputRegion);
3587 				view->PushState();
3588 
3589 				// Highlight in blue the inputted text
3590 				view->SetHighColor(kBlueInputColor);
3591 				view->FillRect(textRegion.Frame());
3592 
3593 				// Highlight in red the selected part
3594 				if (fInline->SelectionLength() > 0) {
3595 					BRegion selectedRegion;
3596 					GetTextRegion(fInline->Offset()
3597 						+ fInline->SelectionOffset(), fInline->Offset()
3598 						+ fInline->SelectionOffset()
3599 						+ fInline->SelectionLength(), &selectedRegion);
3600 
3601 					textRegion.IntersectWith(&selectedRegion);
3602 
3603 					view->SetHighColor(kRedInputColor);
3604 					view->FillRect(textRegion.Frame());
3605 				}
3606 
3607 				view->PopState();
3608 			}
3609 
3610 			int32 returnedBytes = tabChars;
3611 			const char *stringToDraw = fText->GetString(offset,
3612 				&returnedBytes);
3613 
3614 			view->DrawString(stringToDraw, returnedBytes);
3615 			if (foundTab) {
3616 				float penPos = PenLocation().x - fTextRect.left;
3617 				float tabWidth = _ActualTabWidth(penPos);
3618 				if (numTabs > 1)
3619 					tabWidth += ((numTabs - 1) * fTabWidth);
3620 
3621 				view->MovePenBy(tabWidth, 0.0);
3622 				tabChars += numTabs;
3623 			}
3624 
3625 			offset += tabChars;
3626 			length -= tabChars;
3627 			numBytes -= tabChars;
3628 			tabChars = numBytes;
3629 			numTabs = 0;
3630 		} while (foundTab && tabChars > 0);
3631 	}
3632 }
3633 
3634 
3635 void
3636 BTextView::_DrawLines(int32 startLine, int32 endLine, int32 startOffset,
3637 	bool erase)
3638 {
3639 	if (!Window())
3640 		return;
3641 
3642 	// clip the text
3643 	BRect clipRect = Bounds() & fTextRect;
3644 	clipRect.InsetBy(-1, -1);
3645 
3646 	BRegion newClip;
3647 	newClip.Set(clipRect);
3648 	ConstrainClippingRegion(&newClip);
3649 
3650 	// set the low color to the view color so that
3651 	// drawing to a non-white background will work
3652 	SetLowColor(ViewColor());
3653 
3654 	BView *view = NULL;
3655 	if (fOffscreen == NULL)
3656 		view = this;
3657 	else {
3658 		fOffscreen->Lock();
3659 		view = fOffscreen->ChildAt(0);
3660 		view->SetLowColor(ViewColor());
3661 		view->FillRect(view->Bounds(), B_SOLID_LOW);
3662 	}
3663 
3664 	long maxLine = fLines->NumLines() - 1;
3665 	if (startLine < 0)
3666 		startLine = 0;
3667 	if (endLine > maxLine)
3668 		endLine = maxLine;
3669 
3670 	// TODO: See if we can avoid this
3671 	if (fAlignment != B_ALIGN_LEFT)
3672 		erase = true;
3673 
3674 	// Actually hide the caret
3675 	if (fCaretVisible)
3676 		_DrawCaret(fSelStart);
3677 
3678 	BRect eraseRect = clipRect;
3679 	int32 startEraseLine = startLine;
3680 	STELine* line = (*fLines)[startLine];
3681 
3682 	if (erase && startOffset != -1 && fAlignment == B_ALIGN_LEFT) {
3683 		// erase only to the right of startOffset
3684 		startEraseLine++;
3685 		int32 startErase = startOffset;
3686 
3687 		BPoint erasePoint = PointAt(startErase);
3688 		eraseRect.left = erasePoint.x;
3689 		eraseRect.top = erasePoint.y;
3690 		eraseRect.bottom = (line + 1)->origin + fTextRect.top;
3691 
3692 		view->FillRect(eraseRect, B_SOLID_LOW);
3693 
3694 		eraseRect = clipRect;
3695 	}
3696 
3697 	BRegion inputRegion;
3698 	if (fInline != NULL && fInline->IsActive())
3699 		GetTextRegion(fInline->Offset(), fInline->Offset() + fInline->Length(), &inputRegion);
3700 
3701 	//BPoint leftTop(startLeft, line->origin);
3702 	for (int32 lineNum = startLine; lineNum <= endLine; lineNum++) {
3703 		const bool eraseThisLine = erase && lineNum >= startEraseLine;
3704 		_DrawLine(view, lineNum, startOffset, eraseThisLine, eraseRect, inputRegion);
3705 		startOffset = -1;
3706 			// Set this to -1 so the next iteration will use the line offset
3707 	}
3708 
3709 	// draw the caret/hilite the selection
3710 	if (fActive) {
3711 		if (fSelStart != fSelEnd && fSelectable)
3712 			Highlight(fSelStart, fSelEnd);
3713 		else {
3714 			if (fCaretVisible)
3715 				_DrawCaret(fSelStart);
3716 		}
3717 	}
3718 
3719 	if (fOffscreen != NULL) {
3720 		view->Sync();
3721 		/*BPoint penLocation = view->PenLocation();
3722 		BRect drawRect(leftTop.x, leftTop.y, penLocation.x, penLocation.y);
3723 		DrawBitmap(fOffscreen, drawRect, drawRect);*/
3724 		fOffscreen->Unlock();
3725 	}
3726 
3727 	ConstrainClippingRegion(NULL);
3728 }
3729 
3730 
3731 void
3732 BTextView::_DrawCaret(int32 offset)
3733 {
3734 	float lineHeight;
3735 	BPoint caretPoint = PointAt(offset, &lineHeight);
3736 	caretPoint.x = min_c(caretPoint.x, fTextRect.right);
3737 
3738 	BRect caretRect;
3739 	caretRect.left = caretRect.right = caretPoint.x;
3740 	caretRect.top = caretPoint.y;
3741 	caretRect.bottom = caretPoint.y + lineHeight - 1;
3742 
3743 	InvertRect(caretRect);
3744 }
3745 
3746 
3747 inline void
3748 BTextView::_ShowCaret()
3749 {
3750 	if (!fCaretVisible)
3751 		_InvertCaret();
3752 }
3753 
3754 
3755 inline void
3756 BTextView::_HideCaret()
3757 {
3758 	if (fCaretVisible)
3759 		_InvertCaret();
3760 }
3761 
3762 
3763 /*! \brief Inverts the blinking caret status.
3764 	Hides the caret if it is being shown, and if it's hidden, shows it.
3765 */
3766 void
3767 BTextView::_InvertCaret()
3768 {
3769 	_DrawCaret(fSelStart);
3770 	fCaretVisible = !fCaretVisible;
3771 	fCaretTime = system_time();
3772 }
3773 
3774 
3775 /*! \brief Place the dragging caret at the given offset.
3776 	\param offset The offset (zero based within the object's text) where to place
3777 	the dragging caret. If it's -1, hide the caret.
3778 */
3779 void
3780 BTextView::_DragCaret(int32 offset)
3781 {
3782 	// does the caret need to move?
3783 	if (offset == fDragOffset)
3784 		return;
3785 
3786 	// hide the previous drag caret
3787 	if (fDragOffset != -1)
3788 		_DrawCaret(fDragOffset);
3789 
3790 	// do we have a new location?
3791 	if (offset != -1) {
3792 		if (fActive) {
3793 			// ignore if offset is within active selection
3794 			if (offset >= fSelStart && offset <= fSelEnd) {
3795 				fDragOffset = -1;
3796 				return;
3797 			}
3798 		}
3799 
3800 		_DrawCaret(offset);
3801 	}
3802 
3803 	fDragOffset = offset;
3804 }
3805 
3806 
3807 void
3808 BTextView::_StopMouseTracking()
3809 {
3810 	delete fTrackingMouse;
3811 	fTrackingMouse = NULL;
3812 }
3813 
3814 
3815 bool
3816 BTextView::_PerformMouseUp(BPoint where)
3817 {
3818 	if (fTrackingMouse == NULL)
3819 		return false;
3820 
3821 	if (fTrackingMouse->selectionRect.IsValid()
3822 		&& fTrackingMouse->selectionRect.Contains(where))
3823 		Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset);
3824 
3825 	_StopMouseTracking();
3826 
3827 	return true;
3828 }
3829 
3830 
3831 bool
3832 BTextView::_PerformMouseMoved(BPoint where, uint32 code)
3833 {
3834 	fWhere = where;
3835 
3836 	if (fTrackingMouse == NULL)
3837 		return false;
3838 
3839 	if (fTrackingMouse->selectionRect.IsValid()
3840 		&& fTrackingMouse->selectionRect.Contains(where)) {
3841 		_StopMouseTracking();
3842 		_InitiateDrag();
3843 		return true;
3844 	}
3845 
3846 	int32 oldOffset = fTrackingMouse->anchor;
3847 	int32 currentOffset = OffsetAt(where);
3848 
3849 	switch (fClickCount) {
3850 		case 0:
3851 			// triple click, select line by line
3852 			fTrackingMouse->selStart = (*fLines)[LineAt(fTrackingMouse->selStart)]->offset;
3853 			fTrackingMouse->selEnd = (*fLines)[LineAt(fTrackingMouse->selEnd) + 1]->offset;
3854 			break;
3855 
3856 		case 2:
3857 			// double click, select word by word
3858 			FindWord(currentOffset, &fTrackingMouse->selStart, &fTrackingMouse->selEnd);
3859 			break;
3860 
3861 		default:
3862 			// new click, select char by char
3863 			if (oldOffset < currentOffset) {
3864 				fTrackingMouse->selStart = oldOffset;
3865 				fTrackingMouse->selEnd = currentOffset;
3866 			} else {
3867 				fTrackingMouse->selStart = currentOffset;
3868 				fTrackingMouse->selEnd = oldOffset;
3869 			}
3870 			break;
3871 	}
3872 
3873 	Select(fTrackingMouse->selStart, fTrackingMouse->selEnd);
3874 	_TrackMouse(where, NULL);
3875 
3876 	return true;
3877 }
3878 
3879 
3880 /*! \brief Tracks the mouse position, doing special actions like changing the
3881 		view cursor.
3882 	\param where The point where the mouse has moved.
3883 	\param message The dragging message, if there is any.
3884 	\param force Passed as second parameter of SetViewCursor()
3885 */
3886 void
3887 BTextView::_TrackMouse(BPoint where, const BMessage *message, bool force)
3888 {
3889 	BRegion textRegion;
3890 	GetTextRegion(fSelStart, fSelEnd, &textRegion);
3891 
3892 	if (message && AcceptsDrop(message))
3893 		_TrackDrag(where);
3894 	else if ((fSelectable || fEditable) && !textRegion.Contains(where))
3895 		SetViewCursor(B_CURSOR_I_BEAM, force);
3896 	else
3897 		SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force);
3898 }
3899 
3900 
3901 /*! \brief Tracks the mouse position when the user is dragging some data.
3902 	\param where The point where the mouse has moved.
3903 */
3904 void
3905 BTextView::_TrackDrag(BPoint where)
3906 {
3907 	CALLED();
3908 	if (Bounds().Contains(where))
3909 		_DragCaret(OffsetAt(where));
3910 }
3911 
3912 
3913 /*! \brief Function called to initiate a drag operation.
3914 */
3915 void
3916 BTextView::_InitiateDrag()
3917 {
3918 	BMessage dragMessage(B_MIME_DATA);
3919 	BBitmap *dragBitmap = NULL;
3920 	BPoint bitmapPoint;
3921 	BHandler *dragHandler = NULL;
3922 
3923 	GetDragParameters(&dragMessage, &dragBitmap, &bitmapPoint, &dragHandler);
3924 	SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
3925 
3926 	if (dragBitmap != NULL)
3927 		DragMessage(&dragMessage, dragBitmap, bitmapPoint, dragHandler);
3928 	else {
3929 		BRegion region;
3930 		GetTextRegion(fSelStart, fSelEnd, &region);
3931 		BRect bounds = Bounds();
3932 		BRect dragRect = region.Frame();
3933 		if (!bounds.Contains(dragRect))
3934 			dragRect = bounds & dragRect;
3935 
3936 		DragMessage(&dragMessage, dragRect, dragHandler);
3937 	}
3938 
3939 	BMessenger messenger(this);
3940 	BMessage message(_DISPOSE_DRAG_);
3941 	fDragRunner = new (nothrow) BMessageRunner(messenger, &message, 100000);
3942 }
3943 
3944 
3945 /*! \brief Called when some data is dropped on the view.
3946 	\param inMessage The message which has been dropped.
3947 	\param where The location where the message has been dropped.
3948 	\param offset ?
3949 	\return \c true if the message was handled, \c false if not.
3950 */
3951 bool
3952 BTextView::_MessageDropped(BMessage *inMessage, BPoint where, BPoint offset)
3953 {
3954 	ASSERT(inMessage);
3955 
3956 	void *from = NULL;
3957 	bool internalDrop = false;
3958 	if (inMessage->FindPointer("be:originator", &from) == B_OK
3959 			&& from == this && fSelEnd != fSelStart)
3960 		internalDrop = true;
3961 
3962 	_DragCaret(-1);
3963 
3964 	delete fDragRunner;
3965 	fDragRunner = NULL;
3966 
3967 	_TrackMouse(where, NULL);
3968 
3969 	// are we sure we like this message?
3970 	if (!AcceptsDrop(inMessage))
3971 		return false;
3972 
3973 	int32 dropOffset = OffsetAt(where);
3974 	if (dropOffset > TextLength())
3975 		dropOffset = TextLength();
3976 
3977 	// if this view initiated the drag, move instead of copy
3978 	if (internalDrop) {
3979 		// dropping onto itself?
3980 		if (dropOffset >= fSelStart && dropOffset <= fSelEnd)
3981 			return true;
3982 	}
3983 
3984 	ssize_t dataLen = 0;
3985 	const char *text = NULL;
3986 	if (inMessage->FindData("text/plain", B_MIME_TYPE, (const void **)&text, &dataLen) == B_OK) {
3987 		text_run_array *runArray = NULL;
3988 		ssize_t runLen = 0;
3989 		if (fStylable)
3990 			inMessage->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
3991 					(const void **)&runArray, &runLen);
3992 
3993 		if (fUndo) {
3994 			delete fUndo;
3995 			fUndo = new _BDropUndoBuffer_(this, text, dataLen, runArray, runLen, dropOffset, internalDrop);
3996 		}
3997 
3998 		if (internalDrop) {
3999 			if (dropOffset > fSelEnd)
4000 				dropOffset -= dataLen;
4001 			Delete();
4002 		}
4003 
4004 		Insert(dropOffset, text, dataLen, runArray);
4005 	}
4006 
4007 	return true;
4008 }
4009 
4010 
4011 void
4012 BTextView::_PerformAutoScrolling()
4013 {
4014 	// Scroll the view a bit if mouse is outside the view bounds
4015 	BRect bounds = Bounds();
4016 	BPoint scrollBy;
4017 
4018 	BPoint constraint = fWhere;
4019 	constraint.ConstrainTo(bounds);
4020 	// Scroll char by char horizontally
4021 	// TODO: Check how BeOS R5 behaves
4022 	float value = _StyledWidthUTF8Safe(OffsetAt(constraint), 1);
4023 	if (fWhere.x > bounds.right) {
4024 		if (bounds.right + value <= fTextRect.Width())
4025 			scrollBy.x = value;
4026 	} else if (fWhere.x < bounds.left) {
4027 		if (bounds.left - value >= 0)
4028 			scrollBy.x = -value;
4029 	}
4030 
4031 	float lineHeight = 0;
4032 	float vertDiff = 0;
4033 	if (fWhere.y > bounds.bottom) {
4034 		lineHeight = LineHeight(LineAt(bounds.LeftBottom()));
4035 		vertDiff = fWhere.y - bounds.bottom;
4036 	} else if (fWhere.y < bounds.top) {
4037 		lineHeight = LineHeight(LineAt(bounds.LeftTop()));
4038 		vertDiff = fWhere.y - bounds.top; // negative value
4039 	}
4040 
4041 	// Always scroll vertically line by line or by multiples of that
4042 	// based on the distance of the cursor from the border of the view
4043 	// TODO: Refine this, I can't even remember how beos works here
4044 	scrollBy.y = lineHeight > 0 ? lineHeight * (int32)(floorf(vertDiff) / lineHeight) : 0;
4045 
4046 	if (bounds.bottom + scrollBy.y > fTextRect.Height())
4047 		scrollBy.y = fTextRect.Height() - bounds.bottom;
4048 	else if (bounds.top + scrollBy.y < 0)
4049 		scrollBy.y = -bounds.top;
4050 
4051 	if (scrollBy != B_ORIGIN)
4052 		ScrollBy(scrollBy.x, scrollBy.y);
4053 }
4054 
4055 
4056 /*! \brief Updates the scrollbars associated with the object (if any).
4057 */
4058 void
4059 BTextView::_UpdateScrollbars()
4060 {
4061 	BRect bounds(Bounds());
4062 	BScrollBar *horizontalScrollBar = ScrollBar(B_HORIZONTAL);
4063  	BScrollBar *verticalScrollBar = ScrollBar(B_VERTICAL);
4064 
4065 	// do we have a horizontal scroll bar?
4066 	if (horizontalScrollBar != NULL) {
4067 		long viewWidth = bounds.IntegerWidth();
4068 		long dataWidth = fTextRect.IntegerWidth();
4069 		dataWidth += (long)ceilf(fTextRect.left) + 1;
4070 
4071 		long maxRange = dataWidth - viewWidth;
4072 		maxRange = max_c(maxRange, 0);
4073 
4074 		horizontalScrollBar->SetRange(0, (float)maxRange);
4075 		horizontalScrollBar->SetProportion((float)viewWidth / (float)dataWidth);
4076 		horizontalScrollBar->SetSteps(10, dataWidth / 10);
4077 	}
4078 
4079 	// how about a vertical scroll bar?
4080 	if (verticalScrollBar != NULL) {
4081 		long viewHeight = bounds.IntegerHeight();
4082 		long dataHeight = fTextRect.IntegerHeight();
4083 		dataHeight += (long)ceilf(fTextRect.top) + 1;
4084 
4085 		long maxRange = dataHeight - viewHeight;
4086 		maxRange = max_c(maxRange, 0);
4087 
4088 		verticalScrollBar->SetRange(0, maxRange);
4089 		verticalScrollBar->SetProportion((float)viewHeight / (float)dataHeight);
4090 		verticalScrollBar->SetSteps(12, viewHeight);
4091 	}
4092 }
4093 
4094 
4095 /*!	\brief Autoresizes the view to fit the contained text.
4096 */
4097 void
4098 BTextView::_AutoResize(bool redraw)
4099 {
4100 	if (fResizable) {
4101 		float oldWidth = Bounds().Width() + 1;
4102 		float newWidth = 3;
4103 		for (int32 i = 0; i < CountLines(); i++)
4104 			newWidth += LineWidth(i);
4105 
4106 		BRect newRect(0, 0, ceilf(newWidth), ceilf(LineHeight(0)) + 2);
4107 
4108 		if (fContainerView != NULL) {
4109 			fContainerView->ResizeTo(newRect.Width() + 1, newRect.Height());
4110 			if (fAlignment == B_ALIGN_CENTER)
4111 				fContainerView->MoveBy(ceilf((oldWidth - (newRect.Width() + 1)) / 2), 0);
4112 			else if (fAlignment == B_ALIGN_RIGHT)
4113 				fContainerView->MoveBy(oldWidth - (newRect.Width() + 1), 0);
4114 			fContainerView->Invalidate();
4115 		}
4116 
4117 		fTextRect = newRect.InsetBySelf(0, 1);
4118 
4119 		if (redraw)
4120 			_DrawLines(0, 0);
4121 
4122 		// Erase the old text (TODO: Might not work for alignments different than B_ALIGN_LEFT)
4123 		SetLowColor(ViewColor());
4124 		FillRect(BRect(fTextRect.right, fTextRect.top, Bounds().right, fTextRect.bottom), B_SOLID_LOW);
4125 	}
4126 }
4127 
4128 
4129 /*! \brief Creates a new offscreen BBitmap with an associated BView.
4130 	param padding Padding (?)
4131 
4132 	Creates an offscreen BBitmap which will be used to draw.
4133 */
4134 void
4135 BTextView::_NewOffscreen(float padding)
4136 {
4137 	if (fOffscreen != NULL)
4138 		_DeleteOffscreen();
4139 
4140 #if USE_DOUBLEBUFFERING
4141 	BRect bitmapRect(0, 0, fTextRect.Width() + padding, fTextRect.Height());
4142 	fOffscreen = new BBitmap(bitmapRect, fColorSpace, true, false);
4143 	if (fOffscreen != NULL && fOffscreen->Lock()) {
4144 		BView *bufferView = new BView(bitmapRect, "drawing view", 0, 0);
4145 		fOffscreen->AddChild(bufferView);
4146 		fOffscreen->Unlock();
4147 	}
4148 #endif
4149 }
4150 
4151 
4152 /*! \brief Deletes the textview's offscreen bitmap, if any.
4153 */
4154 void
4155 BTextView::_DeleteOffscreen()
4156 {
4157 	if (fOffscreen != NULL && fOffscreen->Lock()) {
4158 		delete fOffscreen;
4159 		fOffscreen = NULL;
4160 	}
4161 }
4162 
4163 
4164 /*!	\brief Creates a new offscreen bitmap, highlight the selection, and set the
4165 	cursor to B_CURSOR_I_BEAM.
4166 */
4167 void
4168 BTextView::_Activate()
4169 {
4170 	fActive = true;
4171 
4172 	// Create a new offscreen BBitmap
4173 	_NewOffscreen();
4174 
4175 	if (fSelStart != fSelEnd) {
4176 		if (fSelectable)
4177 			Highlight(fSelStart, fSelEnd);
4178 	} else {
4179 		if (fEditable)
4180 			_ShowCaret();
4181 	}
4182 
4183 	BPoint where;
4184 	ulong buttons;
4185 	GetMouse(&where, &buttons, false);
4186 	if (Bounds().Contains(where))
4187 		_TrackMouse(where, NULL);
4188 }
4189 
4190 
4191 /*! \brief Unhilights the selection, set the cursor to B_CURSOR_SYSTEM_DEFAULT.
4192 */
4193 void
4194 BTextView::_Deactivate()
4195 {
4196 	fActive = false;
4197 
4198 	_CancelInputMethod();
4199 	_DeleteOffscreen();
4200 
4201 	if (fSelStart != fSelEnd) {
4202 		if (fSelectable)
4203 			Highlight(fSelStart, fSelEnd);
4204 	} else
4205 		_HideCaret();
4206 }
4207 
4208 
4209 /*! \brief Changes the passed font to be displayable by the object.
4210 	\param font A pointer to the font to normalize.
4211 
4212 	Set font rotation to 0, removes any font flag, set font spacing
4213 	to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8
4214 */
4215 void
4216 BTextView::_NormalizeFont(BFont *font)
4217 {
4218 	if (font) {
4219 		font->SetRotation(0.0f);
4220 		font->SetFlags(0);
4221 		font->SetSpacing(B_BITMAP_SPACING);
4222 		font->SetEncoding(B_UNICODE_UTF8);
4223 	}
4224 }
4225 
4226 
4227 void
4228 BTextView::_SetRunArray(int32 startOffset, int32 endOffset,
4229 	const text_run_array *inRuns)
4230 {
4231 	if (startOffset > endOffset)
4232 		return;
4233 
4234 	const int32 textLength = fText->Length();
4235 
4236 	// pin offsets at reasonable values
4237 	if (startOffset < 0)
4238 		startOffset = 0;
4239 	else if (startOffset > textLength)
4240 		startOffset = textLength;
4241 
4242 	if (endOffset < 0)
4243 		endOffset = 0;
4244 	else if (endOffset > textLength)
4245 		endOffset = textLength;
4246 
4247 	const int32 numStyles = inRuns->count;
4248 	if (numStyles > 0) {
4249 		const text_run *theRun = &inRuns->runs[0];
4250 		for (int32 index = 0; index < numStyles; index++) {
4251 			int32 fromOffset = theRun->offset + startOffset;
4252 			int32 toOffset = endOffset;
4253 			if (index + 1 < numStyles) {
4254 				toOffset = (theRun + 1)->offset + startOffset;
4255 				toOffset = (toOffset > endOffset) ? endOffset : toOffset;
4256 			}
4257 
4258 			BFont font = theRun->font;
4259 			_NormalizeFont(&font);
4260 			fStyles->SetStyleRange(fromOffset, toOffset, textLength,
4261 				B_FONT_ALL, &theRun->font, &theRun->color);
4262 
4263 			theRun++;
4264 		}
4265 		fStyles->InvalidateNullStyle();
4266 	}
4267 }
4268 
4269 
4270 /*! \brief Returns a value which tells if the given character is a separator
4271 	character or not.
4272 	\param offset The offset where the wanted character can be found.
4273 	\return A value which represents the character's classification.
4274 */
4275 uint32
4276 BTextView::_CharClassification(int32 offset) const
4277 {
4278 	// TODO:Should check against a list of characters containing also
4279 	// japanese word breakers.
4280 	// And what about other languages ? Isn't there a better way to check
4281 	// for separator characters ?
4282 	// Andrew suggested to have a look at UnicodeBlockObject.h
4283 	switch (fText->RealCharAt(offset)) {
4284 		case B_SPACE:
4285 		case '_':
4286 		case '.':
4287 		case '\0':
4288 		case B_TAB:
4289 		case B_ENTER:
4290 		case '&':
4291 		case '*':
4292 		case '+':
4293 		case '-':
4294 		case '/':
4295 		case '<':
4296 		case '=':
4297 		case '>':
4298 		case '\\':
4299 		case '^':
4300 		case '|':
4301 			return B_SEPARATOR_CHARACTER;
4302 		default:
4303 			return B_OTHER_CHARACTER;
4304 	}
4305 }
4306 
4307 
4308 /*! \brief Returns the offset of the next UTF8 character within the BTextView's text.
4309 	\param offset The offset where to start looking.
4310 	\return The offset of the next UTF8 character.
4311 */
4312 int32
4313 BTextView::_NextInitialByte(int32 offset) const
4314 {
4315 	int32 textLength = TextLength();
4316 	if (offset >= textLength)
4317 		return textLength;
4318 
4319 	for (++offset; (ByteAt(offset) & 0xC0) == 0x80; ++offset)
4320 		;
4321 
4322 	return offset;
4323 }
4324 
4325 
4326 /*! \brief Returns the offset of the previous UTF8 character within the BTextView's text.
4327 	\param offset The offset where to start looking.
4328 	\return The offset of the previous UTF8 character.
4329 */
4330 int32
4331 BTextView::_PreviousInitialByte(int32 offset) const
4332 {
4333 	if (offset <= 0)
4334 		return 0;
4335 
4336 	int32 count = 6;
4337 
4338 	for (--offset; offset > 0 && count; --offset, --count) {
4339 		if ((ByteAt(offset) & 0xC0) != 0x80)
4340 			break;
4341 	}
4342 
4343 	return count ? offset : 0;
4344 }
4345 
4346 
4347 bool
4348 BTextView::_GetProperty(BMessage *specifier, int32 form, const char *property, BMessage *reply)
4349 {
4350 	CALLED();
4351 	if (strcmp(property, "selection") == 0) {
4352 		reply->what = B_REPLY;
4353 		reply->AddInt32("result", fSelStart);
4354 		reply->AddInt32("result", fSelEnd);
4355 		reply->AddInt32("error", B_OK);
4356 
4357 		return true;
4358 	} else if (strcmp(property, "Text") == 0) {
4359 		if (IsTypingHidden()) {
4360 			// Do not allow stealing passwords via scripting
4361 			beep();
4362 			return false;
4363 		}
4364 
4365 		int32 index, range;
4366 		specifier->FindInt32("index", &index);
4367 		specifier->FindInt32("range", &range);
4368 
4369 		char *buffer = new char[range + 1];
4370 		GetText(index, range, buffer);
4371 
4372 		reply->what = B_REPLY;
4373 		reply->AddString("result", buffer);
4374 		delete buffer;
4375 		reply->AddInt32("error", B_OK);
4376 
4377 		return true;
4378 	} else if (strcmp(property, "text_run_array") == 0)
4379 		return false;
4380 
4381 	return false;
4382 }
4383 
4384 
4385 bool
4386 BTextView::_SetProperty(BMessage *specifier, int32 form, const char *property,
4387 	BMessage *reply)
4388 {
4389 	CALLED();
4390 	if (strcmp(property, "selection") == 0) {
4391 		int32 index, range;
4392 
4393 		specifier->FindInt32("index", &index);
4394 		specifier->FindInt32("range", &range);
4395 
4396 		Select(index, index + range);
4397 
4398 		reply->what = B_REPLY;
4399 		reply->AddInt32("error", B_OK);
4400 
4401 		return true;
4402 	} else if (strcmp(property, "Text") == 0) {
4403 		int32 index, range;
4404 		specifier->FindInt32("index", &index);
4405 		specifier->FindInt32("range", &range);
4406 
4407 		const char *buffer = NULL;
4408 		if (specifier->FindString("data", &buffer) == B_OK)
4409 			InsertText(buffer, range, index, NULL);
4410 		else
4411 			DeleteText(index, range);
4412 
4413 		reply->what = B_REPLY;
4414 		reply->AddInt32("error", B_OK);
4415 
4416 		return true;
4417 	} else if (strcmp(property, "text_run_array") == 0)
4418 		return false;
4419 
4420 	return false;
4421 }
4422 
4423 
4424 bool
4425 BTextView::_CountProperties(BMessage *specifier, int32 form,
4426 	const char *property, BMessage *reply)
4427 {
4428 	CALLED();
4429 	if (strcmp(property, "Text") == 0) {
4430 		reply->what = B_REPLY;
4431 		reply->AddInt32("result", TextLength());
4432 		reply->AddInt32("error", B_OK);
4433 		return true;
4434 	}
4435 
4436 	return false;
4437 }
4438 
4439 
4440 /*! \brief Called when the object receives a B_INPUT_METHOD_CHANGED message.
4441 	\param message A B_INPUT_METHOD_CHANGED message.
4442 */
4443 void
4444 BTextView::_HandleInputMethodChanged(BMessage *message)
4445 {
4446 	// TODO: block input if not editable (Andrew)
4447 	ASSERT(fInline != NULL);
4448 
4449 	const char *string = NULL;
4450 	if (message->FindString("be:string", &string) < B_OK || string == NULL)
4451 		return;
4452 
4453 	_HideCaret();
4454 
4455 	be_app->ObscureCursor();
4456 
4457 	// If we find the "be:confirmed" boolean (and the boolean is true),
4458 	// it means it's over for now, so the current _BInlineInput_ object
4459 	// should become inactive. We will probably receive a B_INPUT_METHOD_STOPPED
4460 	// message after this one.
4461 	bool confirmed;
4462 	if (message->FindBool("be:confirmed", &confirmed) != B_OK)
4463 		confirmed = false;
4464 
4465 	// Delete the previously inserted text (if any)
4466 	if (fInline->IsActive()) {
4467 		const int32 oldOffset = fInline->Offset();
4468 		DeleteText(oldOffset, oldOffset + fInline->Length());
4469 		if (confirmed)
4470 			fInline->SetActive(false);
4471 		fClickOffset = fSelStart = fSelEnd = oldOffset;
4472 	}
4473 
4474 	const int32 stringLen = strlen(string);
4475 
4476 	fInline->SetOffset(fSelStart);
4477 	fInline->SetLength(stringLen);
4478 	fInline->ResetClauses();
4479 
4480 	if (!confirmed && !fInline->IsActive())
4481 		fInline->SetActive(true);
4482 
4483 	// Get the clauses, and pass them to the _BInlineInput_ object
4484 	// TODO: Find out if what we did it's ok, currently we don't consider clauses
4485 	// at all, while the bebook says we should; though the visual effect we obtained
4486 	// seems correct. Weird.
4487 	int32 clauseCount = 0;
4488 	int32 clauseStart;
4489 	int32 clauseEnd;
4490 	while (message->FindInt32("be:clause_start", clauseCount, &clauseStart) == B_OK &&
4491 			message->FindInt32("be:clause_end", clauseCount, &clauseEnd) == B_OK) {
4492 		if (!fInline->AddClause(clauseStart, clauseEnd))
4493 			break;
4494 		clauseCount++;
4495 	}
4496 
4497 	int32 selectionStart = 0;
4498 	int32 selectionEnd = 0;
4499 	message->FindInt32("be:selection", 0, &selectionStart);
4500 	message->FindInt32("be:selection", 1, &selectionEnd);
4501 
4502 	fInline->SetSelectionOffset(selectionStart);
4503 	fInline->SetSelectionLength(selectionEnd - selectionStart);
4504 
4505 	const int32 inlineOffset = fInline->Offset();
4506 	InsertText(string, stringLen, fSelStart, NULL);
4507 	fSelStart += stringLen;
4508 	fClickOffset = fSelEnd = fSelStart;
4509 
4510 	_Refresh(inlineOffset, fSelEnd, true, true);
4511 
4512 	_ShowCaret();
4513 }
4514 
4515 
4516 /*! \brief Called when the object receives a B_INPUT_METHOD_LOCATION_REQUEST
4517 		message.
4518 */
4519 void
4520 BTextView::_HandleInputMethodLocationRequest()
4521 {
4522 	ASSERT(fInline != NULL);
4523 
4524 	int32 offset = fInline->Offset();
4525 	const int32 limit = offset + fInline->Length();
4526 
4527 	BMessage message(B_INPUT_METHOD_EVENT);
4528 	message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
4529 
4530 	// Add the location of the UTF8 characters
4531 	while (offset < limit) {
4532 		float height;
4533 		BPoint where = PointAt(offset, &height);
4534 		ConvertToScreen(&where);
4535 
4536 		message.AddPoint("be:location_reply", where);
4537 		message.AddFloat("be:height_reply", height);
4538 
4539 		offset = _NextInitialByte(offset);
4540 	}
4541 
4542 	fInline->Method()->SendMessage(&message);
4543 }
4544 
4545 
4546 /*! \brief Tells the input server method addon to stop the current transaction.
4547 */
4548 void
4549 BTextView::_CancelInputMethod()
4550 {
4551 	if (!fInline)
4552 		return;
4553 
4554 	_BInlineInput_ *inlineInput = fInline;
4555 	fInline = NULL;
4556 
4557 	if (inlineInput->IsActive() && Window())
4558 		_Refresh(inlineInput->Offset(), fText->Length() - inlineInput->Offset(), true, false);
4559 
4560 	BMessage message(B_INPUT_METHOD_EVENT);
4561 	message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
4562 	inlineInput->Method()->SendMessage(&message);
4563 
4564 	delete inlineInput;
4565 }
4566 
4567 
4568 /*! \brief Locks the static _BWidthBuffer_ object to be able to access it safely.
4569 */
4570 void
4571 BTextView::LockWidthBuffer()
4572 {
4573 	if (atomic_add(&sWidthAtom, 1) > 0) {
4574 		while (acquire_sem(sWidthSem) == B_INTERRUPTED)
4575 			;
4576 	}
4577 }
4578 
4579 
4580 /*! \brief Unlocks the static _BWidthBuffer_ object.
4581 */
4582 void
4583 BTextView::UnlockWidthBuffer()
4584 {
4585 	if (atomic_add(&sWidthAtom, -1) > 1)
4586 		release_sem(sWidthSem);
4587 }
4588 
4589 
4590 // _BTextTrackState_
4591 _BTextTrackState_::_BTextTrackState_(BMessenger messenger)
4592 	:
4593 	clickOffset(0),
4594 	shiftDown(false),
4595 	anchor(0),
4596 	selStart(0),
4597 	selEnd(0),
4598 	fRunner(NULL)
4599 {
4600 	BMessage message(_PING_);
4601 	fRunner = new (nothrow) BMessageRunner(messenger, &message, 300000);
4602 }
4603 
4604 
4605 _BTextTrackState_::~_BTextTrackState_()
4606 {
4607 	delete fRunner;
4608 }
4609 
4610 
4611 void
4612 _BTextTrackState_::SimulateMouseMovement(BTextView *textView)
4613 {
4614 	BPoint where;
4615 	ulong buttons;
4616 	// When the mouse cursor is still and outside the textview,
4617 	// no B_MOUSE_MOVED message are sent, obviously. But scrolling
4618 	// has to work neverthless, so we "fake" a MouseMoved() call here.
4619 	textView->GetMouse(&where, &buttons);
4620 	textView->_PerformMouseMoved(where, B_INSIDE_VIEW);
4621 }
4622 
4623 
4624