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