1ee694ac8SAxel Dörfler/*
2103adddbSAxel Dörfler * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3ee694ac8SAxel Dörfler * Distributed under the terms of the MIT License.
4ee694ac8SAxel Dörfler *
5ee694ac8SAxel Dörfler * Authors:
6ab21cedcSJohn Scipione *		Stephan A��mus, superstippi@gmx.de
7ab21cedcSJohn Scipione *		Stefano Ceccherini, stefano.ceccherini@gmail.com
8ab21cedcSJohn Scipione *		Marc Flerackers, mflerackers@androme.be
9ee694ac8SAxel Dörfler *		Hiroshi Lockheimer (BTextView is based on his STEEngine)
10eb774c29SJohn Scipione *		John Scipione, jscipione@gmail.com
11ab21cedcSJohn Scipione *		Oliver Tappe, zooey@hirschkaefer.de
12ee694ac8SAxel Dörfler */
13ee694ac8SAxel Dörfler
1472f334d1Sshadow
1513ca2d94SAxel Dörfler// TODOs:
166adf049cSStefano Ceccherini// - Consider using BObjectList instead of BList
17322853c8SStefano Ceccherini// 	 for disallowed characters (it would remove a lot of reinterpret_casts)
1813ca2d94SAxel Dörfler// - Check for correctness and possible optimizations the calls to _Refresh(),
19a682d981SStephan Aßmus// 	 to refresh only changed parts of text (currently we often redraw the whole
20a682d981SStephan Aßmus//   text)
2175ad8970SStefano Ceccherini
2270751f7bSStefano Ceccherini// Known Bugs:
23e005dc75SStefano Ceccherini// - Double buffering doesn't work well (disabled by default)
2470751f7bSStefano Ceccherini
251d1e61bbSJohn Scipione
261d1e61bbSJohn Scipione#include <TextView.h>
271d1e61bbSJohn Scipione
281d1e61bbSJohn Scipione#include <new>
291d1e61bbSJohn Scipione
30ee694ac8SAxel Dörfler#include <stdio.h>
31ee694ac8SAxel Dörfler#include <stdlib.h>
3294b9294fSStefano Ceccherini
3372f334d1Sshadow#include <Application.h>
345a5aa2e3SStefano Ceccherini#include <Beep.h>
35b032f972SStefano Ceccherini#include <Bitmap.h>
3672f334d1Sshadow#include <Clipboard.h>
376adf049cSStefano Ceccherini#include <Debug.h>
387c0f5738SRene Gollent#include <Entry.h>
395a5aa2e3SStefano Ceccherini#include <Input.h>
4091f0846fSRyan Leavengood#include <LayoutBuilder.h>
4188643347SStephan Aßmus#include <LayoutUtils.h>
4291b84c77SStefano Ceccherini#include <MessageRunner.h>
437c0f5738SRene Gollent#include <Path.h>
4491f0846fSRyan Leavengood#include <PopUpMenu.h>
4572f334d1Sshadow#include <PropertyInfo.h>
46323ddd57Sshatty#include <Region.h>
47323ddd57Sshatty#include <ScrollBar.h>
4860f75e90SOliver Tappe#include <SystemCatalog.h>
49323ddd57Sshatty#include <Window.h>
5072f334d1Sshadow
5139fbf550SOliver Tappe#include <binary_compatibility/Interface.h>
5239fbf550SOliver Tappe
536adf049cSStefano Ceccherini#include "InlineInput.h"
54b032f972SStefano Ceccherini#include "LineBuffer.h"
55b032f972SStefano Ceccherini#include "StyleBuffer.h"
56b032f972SStefano Ceccherini#include "TextGapBuffer.h"
57b032f972SStefano Ceccherini#include "UndoBuffer.h"
5876f84757SStefano Ceccherini#include "WidthBuffer.h"
59323ddd57Sshatty
6039fbf550SOliver Tappe
6113ca2d94SAxel Dörflerusing namespace std;
6260f75e90SOliver Tappeusing BPrivate::gSystemCatalog;
6391f0846fSRyan Leavengood
6491f0846fSRyan Leavengood
65546208a5SOliver Tappe#undef B_TRANSLATION_CONTEXT
66546208a5SOliver Tappe#define B_TRANSLATION_CONTEXT "TextView"
6791f0846fSRyan Leavengood
6891f0846fSRyan Leavengood
6960f75e90SOliver Tappe#define TRANSLATE(str) \
70eaa5e093SOliver Tappe	gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "TextView")
718b7faba2SStefano Ceccherini
7288643347SStephan Aßmus#undef TRACE
7388643347SStephan Aßmus#undef CALLED
7488643347SStephan Aßmus//#define TRACE_TEXT_VIEW
7588643347SStephan Aßmus#ifdef TRACE_TEXT_VIEW
7688643347SStephan Aßmus#	include <FunctionTracer.h>
7788643347SStephan Aßmus	static int32 sFunctionDepth = -1;
7888643347SStephan Aßmus#	define CALLED(x...)	FunctionTracer _ft("BTextView", __FUNCTION__, \
7988643347SStephan Aßmus							sFunctionDepth)
8088643347SStephan Aßmus#	define TRACE(x...)	{ BString _to; \
8188643347SStephan Aßmus							_to.Append(' ', (sFunctionDepth + 1) * 2); \
8288643347SStephan Aßmus							printf("%s", _to.String()); printf(x); }
837446a252SStefano Ceccherini#else
8488643347SStephan Aßmus#	define CALLED(x...)
8588643347SStephan Aßmus#	define TRACE(x...)
867446a252SStefano Ceccherini#endif
8772f334d1Sshadow
8888643347SStephan Aßmus
89c6811df0SStephan Aßmus#define USE_WIDTHBUFFER 1
908b7faba2SStefano Ceccherini#define USE_DOUBLEBUFFERING 0
918b7faba2SStefano Ceccherini
928b7faba2SStefano Ceccherini
93323ddd57Sshattystruct flattened_text_run {
94323ddd57Sshatty	int32	offset;
958b7faba2SStefano Ceccherini	font_family	family;
968b7faba2SStefano Ceccherini	font_style style;
97323ddd57Sshatty	float	size;
98b55f61d6SJohn Scipione	float	shear;		// typically 90.0
99b55f61d6SJohn Scipione	uint16	face;		// typically 0
100323ddd57Sshatty	uint8	red;
101323ddd57Sshatty	uint8	green;
102323ddd57Sshatty	uint8	blue;
103b55f61d6SJohn Scipione	uint8	alpha;		// 255 == opaque
104b55f61d6SJohn Scipione	uint16	_reserved_;	// 0
105323ddd57Sshatty};
106323ddd57Sshatty
107323ddd57Sshattystruct flattened_text_run_array {
10813ca2d94SAxel Dörfler	uint32	magic;
10913ca2d94SAxel Dörfler	uint32	version;
11013ca2d94SAxel Dörfler	int32	count;
11113ca2d94SAxel Dörfler	flattened_text_run styles[1];
112323ddd57Sshatty};
113323ddd57Sshatty
11404d40ff2SAxel Dörflerstatic const uint32 kFlattenedTextRunArrayMagic = 'Ali!';
11504d40ff2SAxel Dörflerstatic const uint32 kFlattenedTextRunArrayVersion = 0;
116356acd7dSStefano Ceccherini
11732c29a08SOliver Tappe
118356acd7dSStefano Ceccherinienum {
11932c29a08SOliver Tappe	CHAR_CLASS_DEFAULT,
12032c29a08SOliver Tappe	CHAR_CLASS_WHITESPACE,
12132c29a08SOliver Tappe	CHAR_CLASS_GRAPHICAL,
12232c29a08SOliver Tappe	CHAR_CLASS_QUOTE,
12332c29a08SOliver Tappe	CHAR_CLASS_PUNCTUATION,
12432c29a08SOliver Tappe	CHAR_CLASS_PARENS_OPEN,
12532c29a08SOliver Tappe	CHAR_CLASS_PARENS_CLOSE,
12632c29a08SOliver Tappe	CHAR_CLASS_END_OF_TEXT
127fe23fb66SAxel Dörfler};
128356acd7dSStefano Ceccherini
1297889e7c9SStefano Ceccherini
130a682d981SStephan Aßmusclass BTextView::TextTrackState {
13172f334d1Sshadowpublic:
132a682d981SStephan Aßmus	TextTrackState(BMessenger messenger);
133a682d981SStephan Aßmus	~TextTrackState();
1346343dc98SStefano Ceccherini
135a682d981SStephan Aßmus	void SimulateMouseMovement(BTextView* view);
1366343dc98SStefano Ceccherini
137a682d981SStephan Aßmuspublic:
138a682d981SStephan Aßmus	int32				clickOffset;
139a682d981SStephan Aßmus	bool				shiftDown;
140a682d981SStephan Aßmus	BRect				selectionRect;
1416f260d07SOliver Tappe	BPoint				where;
1423f3ab162SOliver Tappe
143a682d981SStephan Aßmus	int32				anchor;
144a682d981SStephan Aßmus	int32				selStart;
145a682d981SStephan Aßmus	int32				selEnd;
1466343dc98SStefano Ceccherini
1476343dc98SStefano Ceccheriniprivate:
148a682d981SStephan Aßmus	BMessageRunner*		fRunner;
14972f334d1Sshadow};
15075ad8970SStefano Ceccherini
15172f334d1Sshadow
152a682d981SStephan Aßmusstruct BTextView::LayoutData {
15388643347SStephan Aßmus	LayoutData()
15488643347SStephan Aßmus		: leftInset(0),
15588643347SStephan Aßmus		  topInset(0),
15688643347SStephan Aßmus		  rightInset(0),
15788643347SStephan Aßmus		  bottomInset(0),
158a682d981SStephan Aßmus		  valid(false)
159a682d981SStephan Aßmus	{
160a682d981SStephan Aßmus	}
161a682d981SStephan Aßmus
16288643347SStephan Aßmus	void UpdateInsets(const BRect& bounds, const BRect& textRect)
16388643347SStephan Aßmus	{
1644b0f176dSOliver Tappe		// we disallow negative insets, as they would cause parts of the
1654b0f176dSOliver Tappe		// text to be hidden
1664b0f176dSOliver Tappe		leftInset = textRect.left >= bounds.left
1674b0f176dSOliver Tappe			? textRect.left - bounds.left
1684b0f176dSOliver Tappe			: 0;
1694b0f176dSOliver Tappe		topInset = textRect.top >= bounds.top
1704b0f176dSOliver Tappe			? textRect.top - bounds.top
1714b0f176dSOliver Tappe			: 0;
1724b0f176dSOliver Tappe		rightInset = bounds.right >= textRect.right
1734b0f176dSOliver Tappe			? bounds.right - textRect.right
1744b0f176dSOliver Tappe			: leftInset;
1754b0f176dSOliver Tappe		bottomInset = bounds.bottom >= textRect.bottom
1764b0f176dSOliver Tappe			? bounds.bottom - textRect.bottom
1774b0f176dSOliver Tappe			: topInset;
17888643347SStephan Aßmus	}
17988643347SStephan Aßmus
180a682d981SStephan Aßmus	float				leftInset;
181a682d981SStephan Aßmus	float				topInset;
182a682d981SStephan Aßmus	float				rightInset;
183a682d981SStephan Aßmus	float				bottomInset;
184a682d981SStephan Aßmus
185a682d981SStephan Aßmus	BSize				min;
18688643347SStephan Aßmus	BSize				preferred;
187a682d981SStephan Aßmus	bool				valid;
188a682d981SStephan Aßmus};
189a682d981SStephan Aßmus
1906343dc98SStefano Ceccherini
191f287ca7dSOliver Tappestatic const rgb_color kBlueInputColor = { 152, 203, 255, 255 };
192f287ca7dSOliver Tappestatic const rgb_color kRedInputColor = { 255, 152, 152, 255 };
193f287ca7dSOliver Tappe
194f287ca7dSOliver Tappestatic const float kHorizontalScrollBarStep = 10.0;
195f287ca7dSOliver Tappestatic const float kVerticalScrollBarStep = 12.0;
1966343dc98SStefano Ceccherini
197eb774c29SJohn Scipionestatic const int32 kMsgNavigateArrow = '_NvA';
198eb774c29SJohn Scipionestatic const int32 kMsgNavigatePage  = '_NvP';
19917c9e987SKacper Kasperstatic const int32 kMsgRemoveWord    = '_RmW';
200402c3b2cSOliver Tappe
201402c3b2cSOliver Tappe
20213ca2d94SAxel Dörflerstatic property_info sPropertyList[] = {
20372f334d1Sshadow	{
204f4fc3d62SJérôme Duval		"selection",
20572f334d1Sshadow		{ B_GET_PROPERTY, 0 },
20672f334d1Sshadow		{ B_DIRECT_SPECIFIER, 0 },
20772f334d1Sshadow		"Returns the current selection.", 0,
20872f334d1Sshadow		{ B_INT32_TYPE, 0 }
20972f334d1Sshadow	},
21072f334d1Sshadow	{
211f4fc3d62SJérôme Duval		"selection",
21272f334d1Sshadow		{ B_SET_PROPERTY, 0 },
21372f334d1Sshadow		{ B_DIRECT_SPECIFIER, 0 },
21472f334d1Sshadow		"Sets the current selection.", 0,
21572f334d1Sshadow		{ B_INT32_TYPE, 0 }
21672f334d1Sshadow	},
21772f334d1Sshadow	{
21872f334d1Sshadow		"Text",
21972f334d1Sshadow		{ B_COUNT_PROPERTIES, 0 },
22072f334d1Sshadow		{ B_DIRECT_SPECIFIER, 0 },
22172f334d1Sshadow		"Returns the length of the text in bytes.", 0,
22272f334d1Sshadow		{ B_INT32_TYPE, 0 }
22372f334d1Sshadow	},
22472f334d1Sshadow	{
22572f334d1Sshadow		"Text",
22672f334d1Sshadow		{ B_GET_PROPERTY, 0 },
22772f334d1Sshadow		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
22872f334d1Sshadow		"Returns the text in the specified range in the BTextView.", 0,
22972f334d1Sshadow		{ B_STRING_TYPE, 0 }
23072f334d1Sshadow	},
23172f334d1Sshadow	{
23272f334d1Sshadow		"Text",
23372f334d1Sshadow		{ B_SET_PROPERTY, 0 },
23472f334d1Sshadow		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
23572f334d1Sshadow		"Removes or inserts text into the specified range in the BTextView.", 0,
23672f334d1Sshadow		{ B_STRING_TYPE, 0 }
23772f334d1Sshadow	},
23872f334d1Sshadow	{
23972f334d1Sshadow		"text_run_array",
24072f334d1Sshadow		{ B_GET_PROPERTY, 0 },
24172f334d1Sshadow		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
242a682d981SStephan Aßmus		"Returns the style information for the text in the specified range in "
243a682d981SStephan Aßmus			"the BTextView.", 0,
24472f334d1Sshadow		{ B_RAW_TYPE, 0 },
24572f334d1Sshadow	},
24672f334d1Sshadow	{
24772f334d1Sshadow		"text_run_array",
24872f334d1Sshadow		{ B_SET_PROPERTY, 0 },
24972f334d1Sshadow		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
250a682d981SStephan Aßmus		"Sets the style information for the text in the specified range in the "
251a682d981SStephan Aßmus			"BTextView.", 0,
25272f334d1Sshadow		{ B_RAW_TYPE, 0 },
25372f334d1Sshadow	},
254346d1496SHumdinger
25572f334d1Sshadow	{ 0 }
25672f334d1Sshadow};
25772f334d1Sshadow
25876f84757SStefano Ceccherini
259ab21cedcSJohn ScipioneBTextView::BTextView(BRect frame, const char* name, BRect textRect,
260f5c284eeSJohn Scipione	uint32 resizeMask, uint32 flags)
261f5c284eeSJohn Scipione	:
262f5c284eeSJohn Scipione	BView(frame, name, resizeMask,
26313ca2d94SAxel Dörfler		flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
26472f334d1Sshadow{
26513ca2d94SAxel Dörfler	_InitObject(textRect, NULL, NULL);
26672f334d1Sshadow}
2675a5aa2e3SStefano Ceccherini
2685a5aa2e3SStefano Ceccherini
269ab21cedcSJohn ScipioneBTextView::BTextView(BRect frame, const char* name, BRect textRect,
270f5c284eeSJohn Scipione	const BFont* initialFont, const rgb_color* initialColor,
271f5c284eeSJohn Scipione	uint32 resizeMask, uint32 flags)
272f5c284eeSJohn Scipione	:
273f5c284eeSJohn Scipione	BView(frame, name, resizeMask,
27413ca2d94SAxel Dörfler		flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
27572f334d1Sshadow{
27613ca2d94SAxel Dörfler	_InitObject(textRect, initialFont, initialColor);
27772f334d1Sshadow}
27875ad8970SStefano Ceccherini
27975ad8970SStefano Ceccherini
280a682d981SStephan AßmusBTextView::BTextView(const char* name, uint32 flags)
281f5c284eeSJohn Scipione	:
282f5c284eeSJohn Scipione	BView(name,
283f5c284eeSJohn Scipione		flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
284a682d981SStephan Aßmus{
28588643347SStephan Aßmus	_InitObject(Bounds(), NULL, NULL);
286a682d981SStephan Aßmus}
287a682d981SStephan Aßmus
288a682d981SStephan Aßmus
289a682d981SStephan AßmusBTextView::BTextView(const char* name, const BFont* initialFont,
290a682d981SStephan Aßmus	const rgb_color* initialColor, uint32 flags)
291f5c284eeSJohn Scipione	:
292f5c284eeSJohn Scipione	BView(name,
293f5c284eeSJohn Scipione		flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
294a682d981SStephan Aßmus{
29588643347SStephan Aßmus	_InitObject(Bounds(), initialFont, initialColor);
296a682d981SStephan Aßmus}
297a682d981SStephan Aßmus
298a682d981SStephan Aßmus
299ab21cedcSJohn ScipioneBTextView::BTextView(BMessage* archive)
300f5c284eeSJohn Scipione	:
301f5c284eeSJohn Scipione	BView(archive)
30272f334d1Sshadow{
3037446a252SStefano Ceccherini	CALLED();
304323ddd57Sshatty	BRect rect;
305323ddd57Sshatty
306aa4d8c57SStefano Ceccherini	if (archive->FindRect("_trect", &rect) != B_OK)
307aa4d8c57SStefano Ceccherini		rect.Set(0, 0, 0, 0);
308aa4d8c57SStefano Ceccherini
30913ca2d94SAxel Dörfler	_InitObject(rect, NULL, NULL);
3103f3ab162SOliver Tappe
311ab21cedcSJohn Scipione	const char* text = NULL;
312323ddd57Sshatty	if (archive->FindString("_text", &text) == B_OK)
313323ddd57Sshatty		SetText(text);
314323ddd57Sshatty
315356acd7dSStefano Ceccherini	int32 flag, flag2;
316323ddd57Sshatty	if (archive->FindInt32("_align", &flag) == B_OK)
317323ddd57Sshatty		SetAlignment((alignment)flag);
318323ddd57Sshatty
319356acd7dSStefano Ceccherini	float value;
320356acd7dSStefano Ceccherini
321323ddd57Sshatty	if (archive->FindFloat("_tab", &value) == B_OK)
322323ddd57Sshatty		SetTabWidth(value);
3233f3ab162SOliver Tappe
324323ddd57Sshatty	if (archive->FindInt32("_col_sp", &flag) == B_OK)
325323ddd57Sshatty		SetColorSpace((color_space)flag);
326323ddd57Sshatty
327323ddd57Sshatty	if (archive->FindInt32("_max", &flag) == B_OK)
3280ddc8de6Shaydentech		SetMaxBytes(flag);
329323ddd57Sshatty
330323ddd57Sshatty	if (archive->FindInt32("_sel", &flag) == B_OK &&
331323ddd57Sshatty		archive->FindInt32("_sel", &flag2) == B_OK)
332323ddd57Sshatty		Select(flag, flag2);
3333f3ab162SOliver Tappe
334356acd7dSStefano Ceccherini	bool toggle;
335356acd7dSStefano Ceccherini
336323ddd57Sshatty	if (archive->FindBool("_stylable", &toggle) == B_OK)
337323ddd57Sshatty		SetStylable(toggle);
338323ddd57Sshatty
339323ddd57Sshatty	if (archive->FindBool("_auto_in", &toggle) == B_OK)
340323ddd57Sshatty		SetAutoindent(toggle);
341323ddd57Sshatty
342323ddd57Sshatty	if (archive->FindBool("_wrap", &toggle) == B_OK)
343323ddd57Sshatty		SetWordWrap(toggle);
344323ddd57Sshatty
345323ddd57Sshatty	if (archive->FindBool("_nsel", &toggle) == B_OK)
346323ddd57Sshatty		MakeSelectable(!toggle);
347323ddd57Sshatty
348323ddd57Sshatty	if (archive->FindBool("_nedit", &toggle) == B_OK)
349323ddd57Sshatty		MakeEditable(!toggle);
350aa4d8c57SStefano Ceccherini
351aa4d8c57SStefano Ceccherini	ssize_t disallowedCount = 0;
352ab21cedcSJohn Scipione	const int32* disallowedChars = NULL;
353aa4d8c57SStefano Ceccherini	if (archive->FindData("_dis_ch", B_RAW_TYPE,
354ab21cedcSJohn Scipione		(const void**)&disallowedChars, &disallowedCount) == B_OK) {
3553f3ab162SOliver Tappe
356aa4d8c57SStefano Ceccherini		fDisallowedChars = new BList;
357aa4d8c57SStefano Ceccherini		disallowedCount /= sizeof(int32);
358a682d981SStephan Aßmus		for (int32 x = 0; x < disallowedCount; x++) {
359a682d981SStephan Aßmus			fDisallowedChars->AddItem(
360ab21cedcSJohn Scipione				reinterpret_cast<void*>(disallowedChars[x]));
361a682d981SStephan Aßmus		}
362aa4d8c57SStefano Ceccherini	}
3633f3ab162SOliver Tappe
364aa4d8c57SStefano Ceccherini	ssize_t runSize = 0;
365ab21cedcSJohn Scipione	const void* flattenedRun = NULL;
3663f3ab162SOliver Tappe
367a682d981SStephan Aßmus	if (archive->FindData("_runs", B_RAW_TYPE, &flattenedRun, &runSize)
368a682d981SStephan Aßmus			== B_OK) {
369ab21cedcSJohn Scipione		text_run_array* runArray = UnflattenRunArray(flattenedRun,
370a682d981SStephan Aßmus			(int32*)&runSize);
37174dca7b4SStefano Ceccherini		if (runArray) {
37274dca7b4SStefano Ceccherini			SetRunArray(0, TextLength(), runArray);
373e3338433SStefano Ceccherini			FreeRunArray(runArray);
37474dca7b4SStefano Ceccherini		}
375aa4d8c57SStefano Ceccherini	}
37672f334d1Sshadow}
37775ad8970SStefano Ceccherini
37875ad8970SStefano Ceccherini
37972f334d1SshadowBTextView::~BTextView()
38072f334d1Sshadow{
38113ca2d94SAxel Dörfler	_CancelInputMethod();
38213ca2d94SAxel Dörfler	_StopMouseTracking();
38313ca2d94SAxel Dörfler	_DeleteOffscreen();
3846fb34cf9SStefano Ceccherini
38572f334d1Sshadow	delete fText;
38672f334d1Sshadow	delete fLines;
387323ddd57Sshatty	delete fStyles;
388ba926063SStefano Ceccherini	delete fDisallowedChars;
389b140f1deSMarc Flerackers	delete fUndo;
3906343dc98SStefano Ceccherini	delete fClickRunner;
3913f3ab162SOliver Tappe	delete fDragRunner;
39288643347SStephan Aßmus	delete fLayoutData;
39372f334d1Sshadow}
39475ad8970SStefano Ceccherini
39575ad8970SStefano Ceccherini
396ab21cedcSJohn ScipioneBArchivable*
397ab21cedcSJohn ScipioneBTextView::Instantiate(BMessage* archive)
39872f334d1Sshadow{
3997446a252SStefano Ceccherini	CALLED();
40072f334d1Sshadow	if (validate_instantiation(archive, "BTextView"))
40172f334d1Sshadow		return new BTextView(archive);
4025a596083SStefano Ceccherini	return NULL;
40372f334d1Sshadow}
40475ad8970SStefano Ceccherini
40575ad8970SStefano Ceccherini
40613ca2d94SAxel Dörflerstatus_t
407ab21cedcSJohn ScipioneBTextView::Archive(BMessage* data, bool deep) const
40872f334d1Sshadow{
4097446a252SStefano Ceccherini	CALLED();
410323ddd57Sshatty	status_t err = BView::Archive(data, deep);
4115a596083SStefano Ceccherini	if (err == B_OK)
4125a596083SStefano Ceccherini		err = data->AddString("_text", Text());
4135a596083SStefano Ceccherini	if (err == B_OK)
4145a596083SStefano Ceccherini		err = data->AddInt32("_align", fAlignment);
4155a596083SStefano Ceccherini	if (err == B_OK)
4165a596083SStefano Ceccherini		err = data->AddFloat("_tab", fTabWidth);
4175a596083SStefano Ceccherini	if (err == B_OK)
4185a596083SStefano Ceccherini		err = data->AddInt32("_col_sp", fColorSpace);
4195a596083SStefano Ceccherini	if (err == B_OK)
4205a596083SStefano Ceccherini		err = data->AddRect("_trect", fTextRect);
4215a596083SStefano Ceccherini	if (err == B_OK)
4225a596083SStefano Ceccherini		err = data->AddInt32("_max", fMaxBytes);
4235a596083SStefano Ceccherini	if (err == B_OK)
4245a596083SStefano Ceccherini		err = data->AddInt32("_sel", fSelStart);
4255a596083SStefano Ceccherini	if (err == B_OK)
4263f3ab162SOliver Tappe		err = data->AddInt32("_sel", fSelEnd);
4275a596083SStefano Ceccherini	if (err == B_OK)
4285a596083SStefano Ceccherini		err = data->AddBool("_stylable", fStylable);
4295a596083SStefano Ceccherini	if (err == B_OK)
4305a596083SStefano Ceccherini		err = data->AddBool("_auto_in", fAutoindent);
4315a596083SStefano Ceccherini	if (err == B_OK)
4325a596083SStefano Ceccherini		err = data->AddBool("_wrap", fWrap);
4335a596083SStefano Ceccherini	if (err == B_OK)
4345a596083SStefano Ceccherini		err = data->AddBool("_nsel", !fSelectable);
4355a596083SStefano Ceccherini	if (err == B_OK)
4365a596083SStefano Ceccherini		err = data->AddBool("_nedit", !fEditable);
4373f3ab162SOliver Tappe
438e683838eSSean Healy	if (err == B_OK && fDisallowedChars != NULL && fDisallowedChars->CountItems() > 0) {
4395a596083SStefano Ceccherini		err = data->AddData("_dis_ch", B_RAW_TYPE, fDisallowedChars->Items(),
440aa4d8c57SStefano Ceccherini			fDisallowedChars->CountItems() * sizeof(int32));
44113ca2d94SAxel Dörfler	}
442aa4d8c57SStefano Ceccherini
4435a596083SStefano Ceccherini	if (err == B_OK) {
4445a596083SStefano Ceccherini		int32 runSize = 0;
445ab21cedcSJohn Scipione		text_run_array* runArray = RunArray(0, TextLength());
4463f3ab162SOliver Tappe
447ab21cedcSJohn Scipione		void* flattened = FlattenRunArray(runArray, &runSize);
4485a596083SStefano Ceccherini		if (flattened != NULL) {
4493f3ab162SOliver Tappe			data->AddData("_runs", B_RAW_TYPE, flattened, runSize);
4505a596083SStefano Ceccherini			free(flattened);
4515a596083SStefano Ceccherini		} else
4525a596083SStefano Ceccherini			err = B_NO_MEMORY;
4533f3ab162SOliver Tappe
4545a596083SStefano Ceccherini		FreeRunArray(runArray);
4555a596083SStefano Ceccherini	}
4563f3ab162SOliver Tappe
457323ddd57Sshatty	return err;
45872f334d1Sshadow}
45975ad8970SStefano Ceccherini
46075ad8970SStefano Ceccherini
461323ddd57Sshattyvoid
462323ddd57SshattyBTextView::AttachedToWindow()
46372f334d1Sshadow{
464323ddd57Sshatty	BView::AttachedToWindow();
4653f3ab162SOliver Tappe
466f3e0a5e9SStefano Ceccherini	SetDrawingMode(B_OP_COPY);
4673f3ab162SOliver Tappe
46875ad8970SStefano Ceccherini	Window()->SetPulseRate(500000);
4693f3ab162SOliver Tappe
470323ddd57Sshatty	fCaretVisible = false;
471323ddd57Sshatty	fCaretTime = 0;
472323ddd57Sshatty	fClickCount = 0;
473323ddd57Sshatty	fClickTime = 0;
474323ddd57Sshatty	fDragOffset = -1;
475323ddd57Sshatty	fActive = false;
4763f3ab162SOliver Tappe
477b8872c02SStephan Aßmus	_AutoResize(true);
4783f3ab162SOliver Tappe
47913ca2d94SAxel Dörfler	_UpdateScrollbars();
4803f3ab162SOliver Tappe
4815a596083SStefano Ceccherini	SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
48272f334d1Sshadow}
48375ad8970SStefano Ceccherini
48475ad8970SStefano Ceccherini
485323ddd57Sshattyvoid
486323ddd57SshattyBTextView::DetachedFromWindow()
48772f334d1Sshadow{
488b032f972SStefano Ceccherini	BView::DetachedFromWindow();
48972f334d1Sshadow}
49075ad8970SStefano Ceccherini
49175ad8970SStefano Ceccherini
492323ddd57Sshattyvoid
493323ddd57SshattyBTextView::Draw(BRect updateRect)
49472f334d1Sshadow{
495323ddd57Sshatty	// what lines need to be drawn?
496588b46eaSOliver Tappe	int32 startLine = _LineAt(BPoint(0.0, updateRect.top));
497588b46eaSOliver Tappe	int32 endLine = _LineAt(BPoint(0.0, updateRect.bottom));
49872f334d1Sshadow
499b18f133dSOliver Tappe	_DrawLines(startLine, endLine, -1, true);
50072f334d1Sshadow}
50175ad8970SStefano Ceccherini
50275ad8970SStefano Ceccherini
503323ddd57Sshattyvoid
504323ddd57SshattyBTextView::MouseDown(BPoint where)
50572f334d1Sshadow{
506323ddd57Sshatty	// should we even bother?
507b032f972SStefano Ceccherini	if (!fEditable && !fSelectable)
508323ddd57Sshatty		return;
5093f3ab162SOliver Tappe
51013ca2d94SAxel Dörfler	_CancelInputMethod();
5113f3ab162SOliver Tappe
5126fb34cf9SStefano Ceccherini	if (!IsFocus())
513323ddd57Sshatty		MakeFocus();
5143f3ab162SOliver Tappe
51599584ef9SStefano Ceccherini	_HideCaret();
5163f3ab162SOliver Tappe
51713ca2d94SAxel Dörfler	_StopMouseTracking();
5183f3ab162SOliver Tappe
5196e6f8fffSStefano Ceccherini	int32 modifiers = 0;
5207cd8f5f9SPhilippe Saint-Pierre	uint32 buttons = 0;
521ab21cedcSJohn Scipione	BMessage* currentMessage = Window()->CurrentMessage();
5226343dc98SStefano Ceccherini	if (currentMessage != NULL) {
5236e6f8fffSStefano Ceccherini		currentMessage->FindInt32("modifiers", &modifiers);
524ab21cedcSJohn Scipione		currentMessage->FindInt32("buttons", (int32*)&buttons);
52591f0846fSRyan Leavengood	}
52691f0846fSRyan Leavengood
52791f0846fSRyan Leavengood	if (buttons == B_SECONDARY_MOUSE_BUTTON) {
52891f0846fSRyan Leavengood		_ShowContextMenu(where);
52991f0846fSRyan Leavengood		return;
530323ddd57Sshatty	}
5316be70a0fSStefano Ceccherini
53291f0846fSRyan Leavengood	BMessenger messenger(this);
53391f0846fSRyan Leavengood	fTrackingMouse = new (nothrow) TextTrackState(messenger);
53491f0846fSRyan Leavengood	if (fTrackingMouse == NULL)
53591f0846fSRyan Leavengood		return;
53691f0846fSRyan Leavengood
5376343dc98SStefano Ceccherini	fTrackingMouse->clickOffset = OffsetAt(where);
5386343dc98SStefano Ceccherini	fTrackingMouse->shiftDown = modifiers & B_SHIFT_KEY;
5396f260d07SOliver Tappe	fTrackingMouse->where = where;
5406343dc98SStefano Ceccherini
5416343dc98SStefano Ceccherini	bigtime_t clickTime = system_time();
542323ddd57Sshatty	bigtime_t clickSpeed = 0;
543323ddd57Sshatty	get_click_speed(&clickSpeed);
5443f3ab162SOliver Tappe	bool multipleClick
5453f3ab162SOliver Tappe		= clickTime - fClickTime < clickSpeed
5469946ba71SOliver Tappe			&& fLastClickOffset == fTrackingMouse->clickOffset;
5476343dc98SStefano Ceccherini
5483f3ab162SOliver Tappe	fWhere = where;
5495a5aa2e3SStefano Ceccherini
550a682d981SStephan Aßmus	SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS,
551a682d981SStephan Aßmus		B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
5523f3ab162SOliver Tappe
5536343dc98SStefano Ceccherini	if (fSelStart != fSelEnd && !fTrackingMouse->shiftDown && !multipleClick) {
5546343dc98SStefano Ceccherini		BRegion region;
5556343dc98SStefano Ceccherini		GetTextRegion(fSelStart, fSelEnd, &region);
5566343dc98SStefano Ceccherini		if (region.Contains(where)) {
5576343dc98SStefano Ceccherini			// Setup things for dragging
5586343dc98SStefano Ceccherini			fTrackingMouse->selectionRect = region.Frame();
5593f3ab162SOliver Tappe			fClickCount = 1;
5603f3ab162SOliver Tappe			fClickTime = clickTime;
5619946ba71SOliver Tappe			fLastClickOffset = OffsetAt(where);
5626343dc98SStefano Ceccherini			return;
5636343dc98SStefano Ceccherini		}
5646343dc98SStefano Ceccherini	}
5653f3ab162SOliver Tappe
5666343dc98SStefano Ceccherini	if (multipleClick) {
5673f3ab162SOliver Tappe		if (fClickCount > 3) {
568323ddd57Sshatty			fClickCount = 0;
569323ddd57Sshatty			fClickTime = 0;
570b032f972SStefano Ceccherini		} else {
5713f3ab162SOliver Tappe			fClickCount++;
5726343dc98SStefano Ceccherini			fClickTime = clickTime;
573323ddd57Sshatty		}
5743f3ab162SOliver Tappe	} else if (!fTrackingMouse->shiftDown) {
5759946ba71SOliver Tappe		// If no multiple click yet and shift is not pressed, this is an
5769946ba71SOliver Tappe		// independent first click somewhere into the textview - we initialize
5779946ba71SOliver Tappe		// the corresponding members for handling potential multiple clicks:
5789946ba71SOliver Tappe		fLastClickOffset = fCaretOffset = fTrackingMouse->clickOffset;
579323ddd57Sshatty		fClickCount = 1;
5806343dc98SStefano Ceccherini		fClickTime = clickTime;
5816343dc98SStefano Ceccherini
5826343dc98SStefano Ceccherini		// Deselect any previously selected text
5833f3ab162SOliver Tappe		Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset);
584323ddd57Sshatty	}
5853f3ab162SOliver Tappe
5866343dc98SStefano Ceccherini	if (fClickTime == clickTime) {
5876343dc98SStefano Ceccherini		BMessage message(_PING_);
5886343dc98SStefano Ceccherini		message.AddInt64("clickTime", clickTime);
5896343dc98SStefano Ceccherini		delete fClickRunner;
5905b5e713fSStephan Aßmus
5916343dc98SStefano Ceccherini		BMessenger messenger(this);
592a682d981SStephan Aßmus		fClickRunner = new (nothrow) BMessageRunner(messenger, &message,
593a682d981SStephan Aßmus			clickSpeed, 1);
59491b84c77SStefano Ceccherini	}
5955b5e713fSStephan Aßmus
5966343dc98SStefano Ceccherini	if (!fSelectable) {
59713ca2d94SAxel Dörfler		_StopMouseTracking();
5986343dc98SStefano Ceccherini		return;
59913ca2d94SAxel Dörfler	}
6007575ec2dSStefano Ceccherini
6016343dc98SStefano Ceccherini	int32 offset = fSelStart;
6026343dc98SStefano Ceccherini	if (fTrackingMouse->clickOffset > fSelStart)
6036343dc98SStefano Ceccherini		offset = fSelEnd;
6047575ec2dSStefano Ceccherini
6056343dc98SStefano Ceccherini	fTrackingMouse->anchor = offset;
6063f3ab162SOliver Tappe
6073f3ab162SOliver Tappe	MouseMoved(where, B_INSIDE_VIEW, NULL);
608323ddd57Sshatty}
60975ad8970SStefano Ceccherini
61075ad8970SStefano Ceccherini
611323ddd57Sshattyvoid
612323ddd57SshattyBTextView::MouseUp(BPoint where)
613323ddd57Sshatty{
6146343dc98SStefano Ceccherini	BView::MouseUp(where);
61513ca2d94SAxel Dörfler	_PerformMouseUp(where);
6163f3ab162SOliver Tappe
6176343dc98SStefano Ceccherini	delete fDragRunner;
6186343dc98SStefano Ceccherini	fDragRunner = NULL;
619323ddd57Sshatty}
62075ad8970SStefano Ceccherini
62175ad8970SStefano Ceccherini
622323ddd57Sshattyvoid
6231f424632SJohn ScipioneBTextView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
624323ddd57Sshatty{
625b55f61d6SJohn Scipione	// check if it's a "click'n'move"
62613ca2d94SAxel Dörfler	if (_PerformMouseMoved(where, code))
627322853c8SStefano Ceccherini		return;
6282f86ba45SStephan Aßmus
629323ddd57Sshatty	switch (code) {
630323ddd57Sshatty		case B_ENTERED_VIEW:
631323ddd57Sshatty		case B_INSIDE_VIEW:
6321f424632SJohn Scipione			_TrackMouse(where, dragMessage, true);
633323ddd57Sshatty			break;
63413ca2d94SAxel Dörfler
635323ddd57Sshatty		case B_EXITED_VIEW:
63613ca2d94SAxel Dörfler			_DragCaret(-1);
6371f424632SJohn Scipione			if (Window()->IsActive() && dragMessage == NULL)
638b032f972SStefano Ceccherini				SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
639323ddd57Sshatty			break;
64013ca2d94SAxel Dörfler
641323ddd57Sshatty		default:
6421f424632SJohn Scipione			BView::MouseMoved(where, code, dragMessage);
64372f334d1Sshadow	}
64472f334d1Sshadow}
64575ad8970SStefano Ceccherini
64675ad8970SStefano Ceccherini
647323ddd57Sshattyvoid
6481f424632SJohn ScipioneBTextView::WindowActivated(bool active)
64972f334d1Sshadow{
6501f424632SJohn Scipione	BView::WindowActivated(active);
6513f3ab162SOliver Tappe
6521f424632SJohn Scipione	if (active && IsFocus()) {
653323ddd57Sshatty		if (!fActive)
65413ca2d94SAxel Dörfler			_Activate();
655356acd7dSStefano Ceccherini	} else {
656323ddd57Sshatty		if (fActive)
65713ca2d94SAxel Dörfler			_Deactivate();
6586fb34cf9SStefano Ceccherini	}
6593f3ab162SOliver Tappe
6606fb34cf9SStefano Ceccherini	BPoint where;
6619be774b5SAlex Smith	uint32 buttons;
6625ce268a6SAxel Dörfler	GetMouse(&where, &buttons, false);
6633f3ab162SOliver Tappe
6646fb34cf9SStefano Ceccherini	if (Bounds().Contains(where))
66513ca2d94SAxel Dörfler		_TrackMouse(where, NULL);
66672f334d1Sshadow}
66775ad8970SStefano Ceccherini
66875ad8970SStefano Ceccherini
669323ddd57Sshattyvoid
670ab21cedcSJohn ScipioneBTextView::KeyDown(const char* bytes, int32 numBytes)
67172f334d1Sshadow{
672b032f972SStefano Ceccherini	const char keyPressed = bytes[0];
673b032f972SStefano Ceccherini
674ee694ac8SAxel Dörfler	if (!fEditable) {
675ee694ac8SAxel Dörfler		// only arrow and page keys are allowed
676ee694ac8SAxel Dörfler		// (no need to hide the cursor)
677ee694ac8SAxel Dörfler		switch (keyPressed) {
678ee694ac8SAxel Dörfler			case B_LEFT_ARROW:
679ee694ac8SAxel Dörfler			case B_RIGHT_ARROW:
680ee694ac8SAxel Dörfler			case B_UP_ARROW:
681ee694ac8SAxel Dörfler			case B_DOWN_ARROW:
68213ca2d94SAxel Dörfler				_HandleArrowKey(keyPressed);
683ee694ac8SAxel Dörfler				break;
684ee694ac8SAxel Dörfler
685ee694ac8SAxel Dörfler			case B_HOME:
686ee694ac8SAxel Dörfler			case B_END:
687ee694ac8SAxel Dörfler			case B_PAGE_UP:
688ee694ac8SAxel Dörfler			case B_PAGE_DOWN:
68913ca2d94SAxel Dörfler				_HandlePageKey(keyPressed);
690ee694ac8SAxel Dörfler				break;
691ee694ac8SAxel Dörfler
69213ca2d94SAxel Dörfler			default:
693ee694ac8SAxel Dörfler				BView::KeyDown(bytes, numBytes);
694ee694ac8SAxel Dörfler				break;
695ee694ac8SAxel Dörfler		}
696ee694ac8SAxel Dörfler
6975a5aa2e3SStefano Ceccherini		return;
6985a5aa2e3SStefano Ceccherini	}
6995a5aa2e3SStefano Ceccherini
700323ddd57Sshatty	// hide the cursor and caret
7016f260d07SOliver Tappe	if (IsFocus())
7026f260d07SOliver Tappe		be_app->ObscureCursor();
70399584ef9SStefano Ceccherini	_HideCaret();
704ee694ac8SAxel Dörfler
705b032f972SStefano Ceccherini	switch (keyPressed) {
70672f334d1Sshadow		case B_BACKSPACE:
70713ca2d94SAxel Dörfler			_HandleBackspace();
70872f334d1Sshadow			break;
709ee694ac8SAxel Dörfler
71072f334d1Sshadow		case B_LEFT_ARROW:
71172f334d1Sshadow		case B_RIGHT_ARROW:
712323ddd57Sshatty		case B_UP_ARROW:
713323ddd57Sshatty		case B_DOWN_ARROW:
71413ca2d94SAxel Dörfler			_HandleArrowKey(keyPressed);
71572f334d1Sshadow			break;
716ee694ac8SAxel Dörfler
71772f334d1Sshadow		case B_DELETE:
71813ca2d94SAxel Dörfler			_HandleDelete();
71972f334d1Sshadow			break;
720ee694ac8SAxel Dörfler
721323ddd57Sshatty		case B_HOME:
722323ddd57Sshatty		case B_END:
72372f334d1Sshadow		case B_PAGE_UP:
72472f334d1Sshadow		case B_PAGE_DOWN:
72513ca2d94SAxel Dörfler			_HandlePageKey(keyPressed);
726323ddd57Sshatty			break;
727ee694ac8SAxel Dörfler
728323ddd57Sshatty		case B_ESCAPE:
729323ddd57Sshatty		case B_INSERT:
730323ddd57Sshatty		case B_FUNCTION_KEY:
731323ddd57Sshatty			// ignore, pass it up to superclass
732323ddd57Sshatty			BView::KeyDown(bytes, numBytes);
73372f334d1Sshadow			break;
734ee694ac8SAxel Dörfler
73572f334d1Sshadow		default:
736b55f61d6SJohn Scipione			// bail out if the character is not allowed
737ee694ac8SAxel Dörfler			if (fDisallowedChars
738a682d981SStephan Aßmus				&& fDisallowedChars->HasItem(
739ab21cedcSJohn Scipione					reinterpret_cast<void*>((uint32)keyPressed))) {
740ee694ac8SAxel Dörfler				beep();
741ee694ac8SAxel Dörfler				return;
742ee694ac8SAxel Dörfler			}
743ee694ac8SAxel Dörfler
74413ca2d94SAxel Dörfler			_HandleAlphaKey(bytes, numBytes);
745323ddd57Sshatty			break;
746323ddd57Sshatty	}
747ee694ac8SAxel Dörfler
748323ddd57Sshatty	// draw the caret
74999584ef9SStefano Ceccherini	if (fSelStart == fSelEnd)
75099584ef9SStefano Ceccherini		_ShowCaret();
75172f334d1Sshadow}
75275ad8970SStefano Ceccherini
75375ad8970SStefano Ceccherini
754323ddd57Sshattyvoid
755323ddd57SshattyBTextView::Pulse()
75672f334d1Sshadow{
757356acd7dSStefano Ceccherini	if (fActive && fEditable && fSelStart == fSelEnd) {
758323ddd57Sshatty		if (system_time() > (fCaretTime + 500000.0))
75913ca2d94SAxel Dörfler			_InvertCaret();
76072f334d1Sshadow	}
76172f334d1Sshadow}
76275ad8970SStefano Ceccherini
76375ad8970SStefano Ceccherini
764323ddd57Sshattyvoid
7651f424632SJohn ScipioneBTextView::FrameResized(float newWidth, float newHeight)
76672f334d1Sshadow{
7671f424632SJohn Scipione	BView::FrameResized(newWidth, newHeight);
76813ca2d94SAxel Dörfler	_UpdateScrollbars();
76972f334d1Sshadow}
77075ad8970SStefano Ceccherini
77175ad8970SStefano Ceccherini
772323ddd57Sshattyvoid
7731f424632SJohn ScipioneBTextView::MakeFocus(bool focus)
77472f334d1Sshadow{
7751f424632SJohn Scipione	BView::MakeFocus(focus);
7763f3ab162SOliver Tappe
7771f424632SJohn Scipione	if (focus && Window() != NULL && Window()->IsActive()) {
778323ddd57Sshatty		if (!fActive)
77913ca2d94SAxel Dörfler			_Activate();
780356acd7dSStefano Ceccherini	} else {
781323ddd57Sshatty		if (fActive)
78213ca2d94SAxel Dörfler			_Deactivate();
78313ca2d94SAxel Dörfler	}
78472f334d1Sshadow}
78575ad8970SStefano Ceccherini
78675ad8970SStefano Ceccherini
787323ddd57Sshattyvoid
788ab21cedcSJohn ScipioneBTextView::MessageReceived(BMessage* message)
78972f334d1Sshadow{
790b55f61d6SJohn Scipione	// ToDo: block input if not editable (Andrew)
7913f3ab162SOliver Tappe
792323ddd57Sshatty	// was this message dropped?
7933f3ab162SOliver Tappe	if (message->WasDropped()) {
7943f3ab162SOliver Tappe		BPoint dropOffset;
795322853c8SStefano Ceccherini		BPoint dropPoint = message->DropPoint(&dropOffset);
796322853c8SStefano Ceccherini		ConvertFromScreen(&dropPoint);
797322853c8SStefano Ceccherini		ConvertFromScreen(&dropOffset);
79813ca2d94SAxel Dörfler		if (!_MessageDropped(message, dropPoint, dropOffset))
799323ddd57Sshatty			BView::MessageReceived(message);
8003f3ab162SOliver Tappe
801323ddd57Sshatty		return;
802323ddd57Sshatty	}