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