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