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