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