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