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