xref: /haiku/src/kits/interface/TextView.cpp (revision 14e3d1b5768e7110b3d5c0855833267409b71dbb)
1 /*
2  * Copyright 2001-2007, Haiku Inc.
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 (burton666@libero.it)
9  */
10 
11 /**	BTextView displays and manages styled text. */
12 
13 // TODOs:
14 // - Finish documenting this class
15 // - Consider using BObjectList instead of BList
16 // 	 for disallowed characters (it would remove a lot of reinterpret_casts)
17 // - Check for correctness and possible optimizations the calls to Refresh(),
18 // 	 to refresh only changed parts of text (currently we often redraw the whole text)
19 
20 // Known Bugs:
21 // - Double buffering doesn't work well (disabled by default)
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <new>
26 using namespace std;
27 
28 #include <Application.h>
29 #include <Beep.h>
30 #include <Bitmap.h>
31 #include <Clipboard.h>
32 #include <Debug.h>
33 #include <Input.h>
34 #include <MessageRunner.h>
35 #include <PropertyInfo.h>
36 #include <Region.h>
37 #include <ScrollBar.h>
38 #include <TextView.h>
39 #include <Window.h>
40 
41 #include "InlineInput.h"
42 #include "LineBuffer.h"
43 #include "StyleBuffer.h"
44 #include "TextGapBuffer.h"
45 #include "UndoBuffer.h"
46 #include "WidthBuffer.h"
47 
48 
49 //#define TRACE_TEXTVIEW
50 #ifdef TRACE_TEXTVIEW
51 	#define CALLED() printf("%s\n", __PRETTY_FUNCTION__)
52 #else
53 	#define CALLED()
54 #endif
55 
56 
57 #define USE_WIDTHBUFFER 0
58 #define USE_DOUBLEBUFFERING 0
59 
60 
61 struct flattened_text_run {
62 	int32	offset;
63 	font_family	family;
64 	font_style style;
65 	float	size;
66 	float	shear;		/* typically 90.0 */
67 	uint16	face;		/* typically 0 */
68 	uint8	red;
69 	uint8	green;
70 	uint8	blue;
71 	uint8	alpha;		/* 255 == opaque */
72 	uint16	_reserved_;	/* 0 */
73 };
74 
75 struct flattened_text_run_array {
76 	uint32				magic;
77 	uint32				version;
78 	int32				count;
79 	flattened_text_run	styles[1];
80 };
81 
82 static const uint32 kFlattenedTextRunArrayMagic = 'Ali!';
83 static const uint32 kFlattenedTextRunArrayVersion = 0;
84 
85 enum {
86 	B_SEPARATOR_CHARACTER,
87 	B_OTHER_CHARACTER
88 };
89 
90 
91 class _BTextTrackState_ {
92 public:
93 	_BTextTrackState_(BMessenger messenger)
94 		:
95 		clickOffset(0),
96 		shiftDown(false),
97 		anchor(0),
98 		selStart(0),
99 		selEnd(0),
100 		fRunner(NULL)
101 	{
102 		BMessage message(_PING_);
103 		fRunner = new (nothrow) BMessageRunner(messenger, &message, 300000);
104 	}
105 
106 	~_BTextTrackState_()
107 	{
108 		delete fRunner;
109 	}
110 
111 	int32 clickOffset;
112 	bool shiftDown;
113 	BRect selectionRect;
114 
115 	int32 anchor;
116 	int32 selStart;
117 	int32 selEnd;
118 
119 private:
120 	BMessageRunner *fRunner;
121 };
122 
123 
124 // Initialized/finalized by init/fini_interface_kit
125 _BWidthBuffer_* BTextView::sWidths = NULL;
126 sem_id BTextView::sWidthSem = B_BAD_SEM_ID;
127 int32 BTextView::sWidthAtom = 0;
128 
129 
130 const static rgb_color kBlackColor = { 0, 0, 0, 255 };
131 const static rgb_color kBlueInputColor = { 152, 203, 255, 255 };
132 const static rgb_color kRedInputColor = { 255, 152, 152, 255 };
133 
134 
135 static property_info
136 sPropertyList[] = {
137 	{
138 		"selection",
139 		{ B_GET_PROPERTY, 0 },
140 		{ B_DIRECT_SPECIFIER, 0 },
141 		"Returns the current selection.", 0,
142 		{ B_INT32_TYPE, 0 }
143 	},
144 	{
145 		"selection",
146 		{ B_SET_PROPERTY, 0 },
147 		{ B_DIRECT_SPECIFIER, 0 },
148 		"Sets the current selection.", 0,
149 		{ B_INT32_TYPE, 0 }
150 	},
151 	{
152 		"Text",
153 		{ B_COUNT_PROPERTIES, 0 },
154 		{ B_DIRECT_SPECIFIER, 0 },
155 		"Returns the length of the text in bytes.", 0,
156 		{ B_INT32_TYPE, 0 }
157 	},
158 	{
159 		"Text",
160 		{ B_GET_PROPERTY, 0 },
161 		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
162 		"Returns the text in the specified range in the BTextView.", 0,
163 		{ B_STRING_TYPE, 0 }
164 	},
165 	{
166 		"Text",
167 		{ B_SET_PROPERTY, 0 },
168 		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
169 		"Removes or inserts text into the specified range in the BTextView.", 0,
170 		{ B_STRING_TYPE, 0 }
171 	},
172 	{
173 		"text_run_array",
174 		{ B_GET_PROPERTY, 0 },
175 		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
176 		"Returns the style information for the text in the specified range in the BTextView.", 0,
177 		{ B_RAW_TYPE, 0 },
178 	},
179 	{
180 		"text_run_array",
181 		{ B_SET_PROPERTY, 0 },
182 		{ B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
183 		"Sets the style information for the text in the specified range in the BTextView.", 0,
184 		{ B_RAW_TYPE, 0 },
185 	},
186 	{ 0 }
187 };
188 
189 
190 /*! \brief Creates a BTextView object with the given attributes.
191 	\param frame The rect which will enclose the BTextView object.
192 	\param name The name of the object.
193 	\param textRect Determines the area of the text within the BTextView object.
194 	\param resizeMask The resizing mask for the BTextView, passed to the BView constructor.
195 	\param flags The flags for the BTextView, passed to the BView constructor.
196 */
197 BTextView::BTextView(BRect frame, const char *name, BRect textRect,
198 					 uint32 resizeMask, uint32 flags)
199 	:	BView(frame, name, resizeMask,
200 				flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
201 {
202 	InitObject(textRect, NULL, NULL);
203 }
204 
205 
206 /*! \brief Creates a BTextView object with the given attributes.
207 	\param frame The rect which will enclose the BTextView object.
208 	\param name The name of the object.
209 	\param textRect Determines the area of the text within the BTextView object.
210 	\param initialFont The BTextView will display its text using this font, unless otherwise specified.
211 	\param initialColor The BTextView will display its text using this color, unless otherwise specified.
212 	\param resizeMask The resizing mask for the BTextView, passed to the BView constructor.
213 	\param flags The flags for the BTextView, passed to the BView constructor.
214 */
215 BTextView::BTextView(BRect frame, const char *name, BRect textRect,
216 					 const BFont *initialFont, const rgb_color *initialColor,
217 					 uint32 resizeMask, uint32 flags)
218 	:	BView(frame, name, resizeMask,
219 				flags | B_FRAME_EVENTS | B_PULSE_NEEDED | B_INPUT_METHOD_AWARE)
220 {
221 	InitObject(textRect, initialFont, initialColor);
222 }
223 
224 
225 /*! \brief Creates a BTextView object from the passed BMessage.
226 	\param archive The BMessage from which the object shall be created.
227 */
228 BTextView::BTextView(BMessage *archive)
229 	:	BView(archive)
230 {
231 	CALLED();
232 	BRect rect;
233 
234 	if (archive->FindRect("_trect", &rect) != B_OK)
235 		rect.Set(0, 0, 0, 0);
236 
237 	InitObject(rect, NULL, NULL);
238 
239 	const char *text = NULL;
240 	if (archive->FindString("_text", &text) == B_OK)
241 		SetText(text);
242 
243 	int32 flag, flag2;
244 	if (archive->FindInt32("_align", &flag) == B_OK)
245 		SetAlignment((alignment)flag);
246 
247 	float value;
248 
249 	if (archive->FindFloat("_tab", &value) == B_OK)
250 		SetTabWidth(value);
251 
252 	if (archive->FindInt32("_col_sp", &flag) == B_OK)
253 		SetColorSpace((color_space)flag);
254 
255 	if (archive->FindInt32("_max", &flag) == B_OK)
256 		SetMaxBytes(flag);
257 
258 	if (archive->FindInt32("_sel", &flag) == B_OK &&
259 		archive->FindInt32("_sel", &flag2) == B_OK)
260 		Select(flag, flag2);
261 
262 	bool toggle;
263 
264 	if (archive->FindBool("_stylable", &toggle) == B_OK)
265 		SetStylable(toggle);
266 
267 	if (archive->FindBool("_auto_in", &toggle) == B_OK)
268 		SetAutoindent(toggle);
269 
270 	if (archive->FindBool("_wrap", &toggle) == B_OK)
271 		SetWordWrap(toggle);
272 
273 	if (archive->FindBool("_nsel", &toggle) == B_OK)
274 		MakeSelectable(!toggle);
275 
276 	if (archive->FindBool("_nedit", &toggle) == B_OK)
277 		MakeEditable(!toggle);
278 
279 	ssize_t disallowedCount = 0;
280 	const int32 *disallowedChars = NULL;
281 	if (archive->FindData("_dis_ch", B_RAW_TYPE,
282 		(const void **)&disallowedChars, &disallowedCount) == B_OK) {
283 
284 		fDisallowedChars = new BList;
285 		disallowedCount /= sizeof(int32);
286 		for (int32 x = 0; x < disallowedCount; x++)
287 			fDisallowedChars->AddItem(reinterpret_cast<void *>(disallowedChars[x]));
288 	}
289 
290 	ssize_t runSize = 0;
291 	const void *flattenedRun = NULL;
292 
293 	if (archive->FindData("_runs", B_RAW_TYPE, &flattenedRun, &runSize) == B_OK) {
294 		text_run_array *runArray = UnflattenRunArray(flattenedRun, (int32 *)&runSize);
295 		if (runArray) {
296 			SetRunArray(0, TextLength(), runArray);
297 			FreeRunArray(runArray);
298 		}
299 	}
300 
301 }
302 
303 
304 /*! \brief Frees the memory allocated and destroy the object created on construction.
305 */
306 BTextView::~BTextView()
307 {
308 	CancelInputMethod();
309 	StopMouseTracking();
310 	DeleteOffscreen();
311 
312 	delete fText;
313 	delete fLines;
314 	delete fStyles;
315 	delete fDisallowedChars;
316 	delete fUndo;
317 	delete fClickRunner;
318 	delete fDragRunner;
319 }
320 
321 
322 /*! \brief Static function used to istantiate a BTextView object from the given BMessage.
323 	\param archive The BMessage from which the object shall be created.
324 	\return A constructed BTextView as a BArchivable object.
325 */
326 BArchivable *
327 BTextView::Instantiate(BMessage *archive)
328 {
329 	CALLED();
330 	if (validate_instantiation(archive, "BTextView"))
331 		return new BTextView(archive);
332 	return NULL;
333 }
334 
335 
336 /*! \brief Archives the object into the passed message.
337 	\param data A pointer to the message where to archive the object.
338 	\param deep ?
339 	\return \c B_OK if everything went well, an error code if not.
340 */
341 status_t
342 BTextView::Archive(BMessage *data, bool deep) const
343 {
344 	CALLED();
345 	status_t err = BView::Archive(data, deep);
346 	if (err == B_OK)
347 		err = data->AddString("_text", Text());
348 	if (err == B_OK)
349 		err = data->AddInt32("_align", fAlignment);
350 	if (err == B_OK)
351 		err = data->AddFloat("_tab", fTabWidth);
352 	if (err == B_OK)
353 		err = data->AddInt32("_col_sp", fColorSpace);
354 	if (err == B_OK)
355 		err = data->AddRect("_trect", fTextRect);
356 	if (err == B_OK)
357 		err = data->AddInt32("_max", fMaxBytes);
358 	if (err == B_OK)
359 		err = data->AddInt32("_sel", fSelStart);
360 	if (err == B_OK)
361 		err = data->AddInt32("_sel", fSelEnd);
362 	if (err == B_OK)
363 		err = data->AddBool("_stylable", fStylable);
364 	if (err == B_OK)
365 		err = data->AddBool("_auto_in", fAutoindent);
366 	if (err == B_OK)
367 		err = data->AddBool("_wrap", fWrap);
368 	if (err == B_OK)
369 		err = data->AddBool("_nsel", !fSelectable);
370 	if (err == B_OK)
371 		err = data->AddBool("_nedit", !fEditable);
372 
373 	if (err == B_OK && fDisallowedChars != NULL)
374 		err = data->AddData("_dis_ch", B_RAW_TYPE, fDisallowedChars->Items(),
375 			fDisallowedChars->CountItems() * sizeof(int32));
376 
377 	if (err == B_OK) {
378 		int32 runSize = 0;
379 		text_run_array *runArray = RunArray(0, TextLength());
380 
381 		void *flattened = FlattenRunArray(runArray, &runSize);
382 		if (flattened != NULL) {
383 			data->AddData("_runs", B_RAW_TYPE, flattened, runSize);
384 			free(flattened);
385 		} else
386 			err = B_NO_MEMORY;
387 
388 		FreeRunArray(runArray);
389 	}
390 
391 	return err;
392 }
393 
394 
395 /*! \brief Hook function called when the BTextView is added to the
396 	window's view hierarchy.
397 
398 	Set the window's pulse rate to 2 per second and adjust scrollbars if needed
399 */
400 void
401 BTextView::AttachedToWindow()
402 {
403 	BView::AttachedToWindow();
404 
405 	SetDrawingMode(B_OP_COPY);
406 
407 	Window()->SetPulseRate(500000);
408 
409 	fCaretVisible = false;
410 	fCaretTime = 0;
411 	fClickCount = 0;
412 	fClickTime = 0;
413 	fDragOffset = -1;
414 	fActive = false;
415 
416 	if (fResizable)
417 		AutoResize(true);
418 
419 	UpdateScrollbars();
420 
421 	SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
422 }
423 
424 
425 /*! \brief Hook function called when the BTextView is removed from the
426 	window's view hierarchy.
427 */
428 void
429 BTextView::DetachedFromWindow()
430 {
431 	BView::DetachedFromWindow();
432 }
433 
434 
435 /*! \brief Hook function called whenever
436 	the contents of the BTextView need to be (re)drawn.
437 	\param updateRect The rect which needs to be redrawn
438 */
439 void
440 BTextView::Draw(BRect updateRect)
441 {
442 	// what lines need to be drawn?
443 	int32 startLine = LineAt(BPoint(0.0f, updateRect.top));
444 	int32 endLine = LineAt(BPoint(0.0f, updateRect.bottom));
445 
446 	// TODO: DrawLines draw the text over and over, causing the text to
447 	// antialias against itself. In theory we should use an offscreen bitmap
448 	// to draw the text which would eliminate the problem.
449 	//DrawLines(startLine, endLine);
450 	Refresh(OffsetAt(startLine), OffsetAt(endLine + 1), false, false);
451 }
452 
453 
454 /*! \brief Hook function called when a mouse button is clicked while
455 	the cursor is in the view.
456 	\param where The location where the mouse button has been clicked.
457 */
458 void
459 BTextView::MouseDown(BPoint where)
460 {
461 	// should we even bother?
462 	if (!fEditable && !fSelectable)
463 		return;
464 
465 	CancelInputMethod();
466 
467 	if (!IsFocus())
468 		MakeFocus();
469 
470 	// hide the caret if it's visible
471 	if (fCaretVisible)
472 		InvertCaret();
473 
474 	StopMouseTracking();
475 
476 	BMessenger messenger(this);
477 	fTrackingMouse = new (nothrow) _BTextTrackState_(messenger);
478 	if (fTrackingMouse == NULL)
479 		return;
480 
481 	int32 modifiers = 0;
482 	//uint32 buttons;
483 	BMessage *currentMessage = Window()->CurrentMessage();
484 	if (currentMessage != NULL) {
485 		currentMessage->FindInt32("modifiers", &modifiers);
486 		//currentMessage->FindInt32("buttons", (int32 *)&buttons);
487 	}
488 
489 	fTrackingMouse->clickOffset = OffsetAt(where);
490 	fTrackingMouse->shiftDown = modifiers & B_SHIFT_KEY;
491 
492 	bigtime_t clickTime = system_time();
493 	bigtime_t clickSpeed = 0;
494 	get_click_speed(&clickSpeed);
495 	bool multipleClick = false;
496 	if (clickTime - fClickTime < clickSpeed && fClickOffset == fTrackingMouse->clickOffset)
497 		multipleClick = true;
498 
499 	fWhere = where;
500 
501 	SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS,	B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
502 
503 	if (fSelStart != fSelEnd && !fTrackingMouse->shiftDown && !multipleClick) {
504 		BRegion region;
505 		GetTextRegion(fSelStart, fSelEnd, &region);
506 		if (region.Contains(where)) {
507 			// Setup things for dragging
508 			fTrackingMouse->selectionRect = region.Frame();
509 			return;
510 		}
511 	}
512 
513 	if (multipleClick) {
514 		if (fClickCount > 1) {
515 			fClickCount = 0;
516 			fClickTime = 0;
517 		} else {
518 			fClickCount = 2;
519 			fClickTime = clickTime;
520 		}
521 	} else {
522 		fClickOffset = fTrackingMouse->clickOffset;
523 		fClickCount = 1;
524 		fClickTime = clickTime;
525 
526 		// Deselect any previously selected text
527 		if (!fTrackingMouse->shiftDown)
528 			Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset);
529 	}
530 
531 	if (fClickTime == clickTime) {
532 		BMessage message(_PING_);
533 		message.AddInt64("clickTime", clickTime);
534 		delete fClickRunner;
535 
536 		BMessenger messenger(this);
537 		fClickRunner = new (nothrow) BMessageRunner(messenger, &message, clickSpeed, 1);
538 	}
539 
540 
541 	if (!fSelectable) {
542 		StopMouseTracking();
543 		return;
544 	}
545 
546 	int32 offset = fSelStart;
547 	if (fTrackingMouse->clickOffset > fSelStart)
548 		offset = fSelEnd;
549 
550 	fTrackingMouse->anchor = offset;
551 
552 	MouseMoved(where, B_INSIDE_VIEW, NULL);
553 }
554 
555 
556 /*! \brief Hook function called when a mouse button is released while
557 	the cursor is in the view.
558 	\param where The point where the mouse button has been released.
559 
560 	Stops asynchronous mouse tracking
561 */
562 void
563 BTextView::MouseUp(BPoint where)
564 {
565 	BView::MouseUp(where);
566 	PerformMouseUp(where);
567 
568 	delete fDragRunner;
569 	fDragRunner = NULL;
570 }
571 
572 
573 /*! \brief Hook function called whenever the mouse cursor enters, exits
574 	or moves inside the view.
575 	\param where The point where the mouse cursor has moved to.
576 	\param code A code which tells if the mouse entered or exited the view
577 	\param message The message containing dragged information, if any.
578 */
579 void
580 BTextView::MouseMoved(BPoint where, uint32 code, const BMessage *message)
581 {
582 	// Check if it's a "click'n'move
583 	if (PerformMouseMoved(where, code))
584 		return;
585 
586 	bool sync = false;
587 	switch (code) {
588 		// We force a sync when the mouse enters the view
589 		case B_ENTERED_VIEW:
590 			sync = true;
591 			// supposed to fall through
592 		case B_INSIDE_VIEW:
593 			TrackMouse(where, message, sync);
594 			break;
595 
596 		case B_EXITED_VIEW:
597 			DragCaret(-1);
598 			if (Window()->IsActive() && message == NULL)
599 				SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
600 			break;
601 
602 		default:
603 			BView::MouseMoved(where, code, message);
604 			break;
605 	}
606 }
607 
608 
609 /*! \brief Hook function called when the window becomes the active window
610 	or gives up that status.
611 	\param state If true, window has just become active, if false, window has
612 	just become inactive.
613 */
614 void
615 BTextView::WindowActivated(bool state)
616 {
617 	BView::WindowActivated(state);
618 
619 	if (state && IsFocus()) {
620 		if (!fActive)
621 			Activate();
622 	} else {
623 		if (fActive)
624 			Deactivate();
625 	}
626 
627 	BPoint where;
628 	ulong buttons;
629 	GetMouse(&where, &buttons, false);
630 
631 	if (Bounds().Contains(where))
632 		TrackMouse(where, NULL);
633 }
634 
635 
636 /*! \brief Hook function called whenever a key is pressed while the view is
637 	the focus view of the active window.
638 */
639 void
640 BTextView::KeyDown(const char *bytes, int32 numBytes)
641 {
642 	const char keyPressed = bytes[0];
643 
644 	if (!fEditable) {
645 		// only arrow and page keys are allowed
646 		// (no need to hide the cursor)
647 		switch (keyPressed) {
648 			case B_LEFT_ARROW:
649 			case B_RIGHT_ARROW:
650 			case B_UP_ARROW:
651 			case B_DOWN_ARROW:
652 				HandleArrowKey(keyPressed);
653 				break;
654 
655 			case B_HOME:
656 			case B_END:
657 			case B_PAGE_UP:
658 			case B_PAGE_DOWN:
659 				HandlePageKey(keyPressed);
660 				break;
661 
662 			default:
663 				BView::KeyDown(bytes, numBytes);
664 				break;
665 		}
666 
667 		return;
668 	}
669 
670 	// hide the cursor and caret
671 	be_app->ObscureCursor();
672 	if (fCaretVisible)
673 		InvertCaret();
674 
675 	switch (keyPressed) {
676 		case B_BACKSPACE:
677 			HandleBackspace();
678 			break;
679 
680 		case B_LEFT_ARROW:
681 		case B_RIGHT_ARROW:
682 		case B_UP_ARROW:
683 		case B_DOWN_ARROW:
684 			HandleArrowKey(keyPressed);
685 			break;
686 
687 		case B_DELETE:
688 			HandleDelete();
689 			break;
690 
691 		case B_HOME:
692 		case B_END:
693 		case B_PAGE_UP:
694 		case B_PAGE_DOWN:
695 			HandlePageKey(keyPressed);
696 			break;
697 
698 		case B_ESCAPE:
699 		case B_INSERT:
700 		case B_FUNCTION_KEY:
701 			// ignore, pass it up to superclass
702 			BView::KeyDown(bytes, numBytes);
703 			break;
704 
705 		default:
706 			// if the character is not allowed, bail out.
707 			if (fDisallowedChars
708 				&& fDisallowedChars->HasItem(reinterpret_cast<void *>((uint32)keyPressed))) {
709 				beep();
710 				return;
711 			}
712 
713 			HandleAlphaKey(bytes, numBytes);
714 			break;
715 	}
716 
717 	// draw the caret
718 	if (fSelStart == fSelEnd) {
719 		if (!fCaretVisible)
720 			InvertCaret();
721 	}
722 }
723 
724 
725 /*! \brief Hook function called every x microseconds.
726 	It's the function which makes the caret blink.
727 */
728 void
729 BTextView::Pulse()
730 {
731 	if (fActive && fEditable && fSelStart == fSelEnd) {
732 		if (system_time() > (fCaretTime + 500000.0))
733 			InvertCaret();
734 	}
735 }
736 
737 
738 /*! \brief Hook function called when the view's frame is resized.
739 	\param width The new view's width.
740 	\param height The new view's height.
741 
742 	Updates the associated scrollbars.
743 */
744 void
745 BTextView::FrameResized(float width, float height)
746 {
747 	BView::FrameResized(width, height);
748 	UpdateScrollbars();
749 }
750 
751 
752 /*! \brief Highlight/unhighlight the selection when the view gets
753 		or looses the focus.
754 	\param focusState The focus state: true, if the view is getting the focus,
755 		false otherwise.
756 */
757 void
758 BTextView::MakeFocus(bool focusState)
759 {
760 	BView::MakeFocus(focusState);
761 
762 	if (focusState && Window() && Window()->IsActive()) {
763 		if (!fActive)
764 			Activate();
765 	} else {
766 		if (fActive)
767 			Deactivate();
768 	}
769 }
770 
771 
772 /*! \brief Hook function executed every time the BTextView gets a message.
773 	\param message The received message
774 */
775 void
776 BTextView::MessageReceived(BMessage *message)
777 {
778 	// TODO: block input if not editable (Andrew)
779 
780 	// was this message dropped?
781 	if (message->WasDropped()) {
782 		BPoint dropOffset;
783 		BPoint dropPoint = message->DropPoint(&dropOffset);
784 		ConvertFromScreen(&dropPoint);
785 		ConvertFromScreen(&dropOffset);
786 		if (!MessageDropped(message, dropPoint, dropOffset))
787 			BView::MessageReceived(message);
788 
789 		return;
790 	}
791 
792 	switch (message->what) {
793 		case B_CUT:
794 			Cut(be_clipboard);
795 			break;
796 
797 		case B_COPY:
798 			Copy(be_clipboard);
799 			break;
800 
801 		case B_PASTE:
802 			Paste(be_clipboard);
803 			break;
804 
805 		case B_UNDO:
806 			Undo(be_clipboard);
807 			break;
808 
809 		case B_SELECT_ALL:
810 			SelectAll();
811 			break;
812 
813 
814 		case B_INPUT_METHOD_EVENT:
815 		{
816 			int32 opcode;
817 			if (message->FindInt32("be:opcode", &opcode) == B_OK) {
818 				switch (opcode) {
819 					case B_INPUT_METHOD_STARTED:
820 					{
821 						BMessenger messenger;
822 						if (message->FindMessenger("be:reply_to", &messenger) == B_OK) {
823 							ASSERT(fInline == NULL);
824 							fInline = new _BInlineInput_(messenger);
825 						}
826 						break;
827 					}
828 
829 					case B_INPUT_METHOD_STOPPED:
830 						delete fInline;
831 						fInline = NULL;
832 						break;
833 
834 					case B_INPUT_METHOD_CHANGED:
835 						HandleInputMethodChanged(message);
836 						break;
837 
838 					case B_INPUT_METHOD_LOCATION_REQUEST:
839 						HandleInputMethodLocationRequest();
840 						break;
841 
842 					default:
843 						break;
844 				}
845 			}
846 			break;
847 		}
848 
849 		case B_SET_PROPERTY:
850 		case B_GET_PROPERTY:
851 		case B_COUNT_PROPERTIES:
852 		{
853 			BPropertyInfo propInfo(sPropertyList);
854 			BMessage specifier;
855 			const char *property;
856 
857 			if (message->GetCurrentSpecifier(NULL, &specifier) < B_OK ||
858 				specifier.FindString("property", &property) < B_OK)
859 				return;
860 
861 			if (propInfo.FindMatch(message, 0, &specifier, specifier.what,
862 				property) < B_OK) {
863 				BView::MessageReceived(message);
864 				break;
865 			}
866 
867 			switch(message->what) {
868 				case B_GET_PROPERTY:
869 				{
870 					BMessage reply;
871 					GetProperty(&specifier, specifier.what, property, &reply);
872 					message->SendReply(&reply);
873 					break;
874 				}
875 
876 				case B_SET_PROPERTY:
877 				{
878 					BMessage reply;
879 					SetProperty(&specifier, specifier.what, property, &reply);
880 					message->SendReply(&reply);
881 					break;
882 				}
883 
884 				case B_COUNT_PROPERTIES:
885 				{
886 					BMessage reply;
887 					CountProperties(&specifier, specifier.what, property, &reply);
888 					message->SendReply(&reply);
889 					break;
890 				}
891 
892 				default:
893 					break;
894 			}
895 
896 			break;
897 		}
898 
899 		case _PING_:
900 		{
901 			if (message->HasInt64("clickTime")) {
902 				bigtime_t clickTime;
903 				message->FindInt64("clickTime", &clickTime);
904 				if (clickTime == fClickTime) {
905 					if (fSelStart != fSelEnd && fSelectable) {
906 						BRegion region;
907 						GetTextRegion(fSelStart, fSelEnd, &region);
908 						if (region.Contains(fWhere))
909 							TrackMouse(fWhere, NULL);
910 					}
911 					delete fClickRunner;
912 					fClickRunner = NULL;
913 				}
914 			} else
915 				PerformAutoScrolling();
916 			break;
917 		}
918 
919 		case _DISPOSE_DRAG_:
920 			if (fEditable)
921 				TrackDrag(fWhere);
922 			break;
923 
924 		default:
925 			BView::MessageReceived(message);
926 			break;
927 	}
928 }
929 
930 
931 /*! \brief Returns the proper handler for the given scripting message.
932 	\param message The scripting message which needs to be examined.
933 	\param index The index of the specifier
934 	\param specifier The message which contains the specifier
935 	\param what The 'what' field of the specifier message.
936 	\param property The name of the targetted property
937 	\return The proper BHandler for the given scripting message.
938 */
939 BHandler *
940 BTextView::ResolveSpecifier(BMessage *message, int32 index,
941 									  BMessage *specifier, int32 what,
942 									  const char *property)
943 {
944 	BPropertyInfo propInfo(sPropertyList);
945 	BHandler *target = this;
946 
947 	if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK)
948 		target = BView::ResolveSpecifier(message, index, specifier, what, property);
949 
950 	return target;
951 }
952 
953 
954 status_t
955 BTextView::GetSupportedSuites(BMessage *data)
956 {
957 	if (data == NULL)
958 		return B_BAD_VALUE;
959 
960 	status_t err = data->AddString("suites", "suite/vnd.Be-text-view");
961 	if (err != B_OK)
962 		return err;
963 
964 	BPropertyInfo prop_info(sPropertyList);
965 	err = data->AddFlat("messages", &prop_info);
966 
967 	if (err != B_OK)
968 		return err;
969 	return BView::GetSupportedSuites(data);
970 }
971 
972 
973 status_t
974 BTextView::Perform(perform_code d, void *arg)
975 {
976 	return BView::Perform(d, arg);
977 }
978 
979 
980 void
981 BTextView::SetText(const char *inText, const text_run_array *inRuns)
982 {
983 	SetText(inText, inText ? strlen(inText) : 0, inRuns);
984 }
985 
986 
987 void
988 BTextView::SetText(const char *inText, int32 inLength, const text_run_array *inRuns)
989 {
990 	CancelInputMethod();
991 
992 	// hide the caret/unhilite the selection
993 	if (fActive) {
994 		if (fSelStart != fSelEnd)
995 			Highlight(fSelStart, fSelEnd);
996 		else {
997 			if (fCaretVisible)
998 				InvertCaret();
999 		}
1000 	}
1001 
1002 	// remove data from buffer
1003 	if (fText->Length() > 0)
1004 		DeleteText(0, fText->Length()); // TODO: was fText->Length() - 1
1005 
1006 	if (inText != NULL && inLength > 0)
1007 		InsertText(inText, inLength, 0, inRuns);
1008 
1009 	// recalc line breaks and draw the text
1010 	Refresh(0, inLength, true, false);
1011 	fClickOffset = fSelStart = fSelEnd = 0;
1012 	ScrollToOffset(fSelStart);
1013 
1014 	// draw the caret
1015 	if (fActive) {
1016 		if (!fCaretVisible)
1017 			InvertCaret();
1018 	}
1019 }
1020 
1021 
1022 void
1023 BTextView::SetText(BFile *inFile, int32 inOffset, int32 inLength,
1024 						 const text_run_array *inRuns)
1025 {
1026 	CALLED();
1027 
1028 	CancelInputMethod();
1029 
1030 	if (!inFile)
1031 		return;
1032 
1033 	if (fText->Length() > 0)
1034 		DeleteText(0, fText->Length());
1035 
1036 	fText->InsertText(inFile, inOffset, inLength, 0);
1037 
1038 	// update the start offsets of each line below offset
1039 	fLines->BumpOffset(inLength, LineAt(inOffset) + 1);
1040 
1041 	// update the style runs
1042 	fStyles->BumpOffset(inLength, fStyles->OffsetToRun(inOffset - 1) + 1);
1043 
1044 	if (inRuns != NULL)
1045 		SetRunArray(inOffset, inOffset + inLength, inRuns);
1046 	else {
1047 		// apply nullStyle to inserted text
1048 		fStyles->SyncNullStyle(inOffset);
1049 		fStyles->SetStyleRange(inOffset, inOffset + inLength,
1050 							  fText->Length(), B_FONT_ALL, NULL, NULL);
1051 	}
1052 
1053 	// recalc line breaks and draw the text
1054 	Refresh(0, inLength, true, false);
1055 	fClickOffset = fSelStart = fSelEnd = 0;
1056 	ScrollToOffset(fSelStart);
1057 
1058 	// draw the caret
1059 	if (fActive) {
1060 		if (!fCaretVisible)
1061 			InvertCaret();
1062 	}
1063 }
1064 
1065 
1066 void
1067 BTextView::Insert(const char *inText, const text_run_array *inRuns)
1068 {
1069 	if (inText != NULL)
1070 		DoInsertText(inText, strlen(inText), fSelStart, inRuns, NULL);
1071 }
1072 
1073 
1074 void
1075 BTextView::Insert(const char *inText, int32 inLength, const text_run_array *inRuns)
1076 {
1077 	if (inText != NULL && inLength > 0) {
1078 		int32 realLength = strlen(inText);
1079 		DoInsertText(inText, min_c(inLength, realLength), fSelStart, inRuns, NULL);
1080 	}
1081 }
1082 
1083 
1084 void
1085 BTextView::Insert(int32 startOffset, const char *inText, int32 inLength,
1086 					   const text_run_array *inRuns)
1087 {
1088 	CALLED();
1089 
1090 	// do we really need to do anything?
1091 	if (inText != NULL && inLength > 0) {
1092 		int32 realLength = strlen(inText);
1093 		DoInsertText(inText, min_c(inLength, realLength), startOffset, inRuns, NULL);
1094 	}
1095 }
1096 
1097 
1098 /*! \brief Deletes the text within the current selection.
1099 */
1100 void
1101 BTextView::Delete()
1102 {
1103 	Delete(fSelStart, fSelEnd);
1104 }
1105 
1106 
1107 /*! \brief Delets the text comprised within the given offsets.
1108 	\param startOffset The offset of the text to delete.
1109 	\param endOffset The offset where the text to delete ends.
1110 */
1111 void
1112 BTextView::Delete(int32 startOffset, int32 endOffset)
1113 {
1114 	CALLED();
1115 	// anything to delete?
1116 	if (startOffset == endOffset)
1117 		return;
1118 
1119 	// hide the caret/unhilite the selection
1120 	if (fActive) {
1121 		if (fSelStart != fSelEnd)
1122 			Highlight(fSelStart, fSelEnd);
1123 		else {
1124 			if (fCaretVisible)
1125 				InvertCaret();
1126 		}
1127 	}
1128 	// remove data from buffer
1129 	DeleteText(startOffset, endOffset);
1130 
1131 	// Check if the caret needs to be moved
1132 	if (fClickOffset >= endOffset)
1133 		fClickOffset -= (endOffset - startOffset);
1134 	else if (fClickOffset >= startOffset && fClickOffset < endOffset)
1135 		fClickOffset = startOffset;
1136 
1137 	fSelEnd = fSelStart = fClickOffset;
1138 
1139 	// recalc line breaks and draw what's left
1140 	Refresh(startOffset, endOffset, true, true);
1141 
1142 	// draw the caret
1143 	if (fActive) {
1144 		if (!fCaretVisible)
1145 			InvertCaret();
1146 	}
1147 }
1148 
1149 
1150 /*! \brief Returns the BTextView text as a C string.
1151 	\return A pointer to the text.
1152 
1153 	It is possible that the BTextView object had to do some operations
1154 	on the text, to be able to return it as a C string.
1155 	If you need to call Text() repeatedly, you'd better use GetText().
1156 */
1157 const char *
1158 BTextView::Text() const
1159 {
1160 	return fText->Text();
1161 }
1162 
1163 
1164 /*! \brief Returns the length of the BTextView's text.
1165 	\return The length of the text.
1166 */
1167 int32
1168 BTextView::TextLength() const
1169 {
1170 	return fText->Length();
1171 }
1172 
1173 
1174 void
1175 BTextView::GetText(int32 offset, int32 length, char *buffer) const
1176 {
1177 	if (buffer != NULL)
1178 		fText->GetString(offset, length, buffer);
1179 }
1180 
1181 
1182 /*! \brief Returns the character at the given offset.
1183 	\param offset The offset of the wanted character.
1184 	\return The character at the given offset.
1185 */
1186 uchar
1187 BTextView::ByteAt(int32 offset) const
1188 {
1189 	if (offset < 0 || offset >= fText->Length())
1190 		return '\0';
1191 
1192 	return (*fText)[offset];
1193 }
1194 
1195 
1196 /*! \brief Returns the number of lines that the object contains.
1197 	\return The number of lines contained in the BTextView object.
1198 */
1199 int32
1200 BTextView::CountLines() const
1201 {
1202 	return fLines->NumLines();
1203 }
1204 
1205 
1206 /*! \brief Returns the index of the current line.
1207 	\return The index of the current line.
1208 */
1209 int32
1210 BTextView::CurrentLine() const
1211 {
1212 	return LineAt(fSelStart);
1213 }
1214 
1215 
1216 /*! \brief Move the caret to the specified line.
1217 	\param index The index of the line.
1218 */
1219 void
1220 BTextView::GoToLine(int32 index)
1221 {
1222 	CancelInputMethod();
1223 	fSelStart = fSelEnd = fClickOffset = OffsetAt(index);
1224 }
1225 
1226 
1227 /*! \brief Cuts the current selection to the clipboard.
1228 	\param clipboard The clipboard where to copy the cutted text.
1229 */
1230 void
1231 BTextView::Cut(BClipboard *clipboard)
1232 {
1233 	CancelInputMethod();
1234 	if (fUndo) {
1235 		delete fUndo;
1236 		fUndo = new _BCutUndoBuffer_(this);
1237 	}
1238 	Copy(clipboard);
1239 	Delete();
1240 }
1241 
1242 
1243 /*! \brief Copies the current selection to the clipboard.
1244 	\param clipboard The clipboard where to copy the selected text.
1245 */
1246 void
1247 BTextView::Copy(BClipboard *clipboard)
1248 {
1249 	CancelInputMethod();
1250 
1251 	if (clipboard->Lock()) {
1252 		clipboard->Clear();
1253 
1254 		BMessage *clip = clipboard->Data();
1255 		if (clip != NULL) {
1256 			clip->AddData("text/plain", B_MIME_TYPE, Text() + fSelStart,
1257 				fSelEnd - fSelStart);
1258 
1259 			int32 size;
1260 			if (fStylable) {
1261 				text_run_array *runArray = RunArray(fSelStart, fSelEnd, &size);
1262 				clip->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
1263 					runArray, size);
1264 				FreeRunArray(runArray);
1265 			}
1266 			clipboard->Commit();
1267 		}
1268 		clipboard->Unlock();
1269 	}
1270 }
1271 
1272 
1273 /*! \brief Paste the text contained in the clipboard to the BTextView.
1274 	\param clipboard A pointer to the clipboard.
1275 */
1276 void
1277 BTextView::Paste(BClipboard *clipboard)
1278 {
1279 	CALLED();
1280 	CancelInputMethod();
1281 
1282 	if (clipboard->Lock()) {
1283 		BMessage *clip = clipboard->Data();
1284 		if (clip != NULL) {
1285 			const char *text = NULL;
1286 			ssize_t len = 0;
1287 
1288 			if (clip->FindData("text/plain", B_MIME_TYPE,
1289 					(const void **)&text, &len) == B_OK) {
1290 				text_run_array *runArray = NULL;
1291 				ssize_t runLen = 0;
1292 
1293 				if (fStylable)
1294 					clip->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
1295 						(const void **)&runArray, &runLen);
1296 
1297 				if (fUndo) {
1298 					delete fUndo;
1299 					fUndo = new _BPasteUndoBuffer_(this, text, len, runArray, runLen);
1300 				}
1301 
1302 				if (fSelStart != fSelEnd)
1303 					Delete();
1304 
1305 				Insert(text, len, runArray);
1306 			}
1307 		}
1308 
1309 		clipboard->Unlock();
1310 	}
1311 }
1312 
1313 
1314 /*! \brief Deletes the currently selected text.
1315 */
1316 void
1317 BTextView::Clear()
1318 {
1319 	// We always check for fUndo != NULL (not only here),
1320 	// because when fUndo is NULL, undo is deactivated.
1321 	if (fUndo) {
1322 		delete fUndo;
1323 		fUndo = new _BClearUndoBuffer_(this);
1324 	}
1325 
1326 	Delete();
1327 }
1328 
1329 
1330 bool
1331 BTextView::AcceptsPaste(BClipboard *clipboard)
1332 {
1333 	bool result = false;
1334 
1335 	if (fEditable && clipboard && clipboard->Lock()) {
1336 		BMessage *data = clipboard->Data();
1337 		result = data && data->HasData("text/plain", B_MIME_TYPE);
1338 		clipboard->Unlock();
1339 	}
1340 
1341 	return result;
1342 }
1343 
1344 
1345 bool
1346 BTextView::AcceptsDrop(const BMessage *inMessage)
1347 {
1348 	if (fEditable && inMessage && inMessage->HasData("text/plain", B_MIME_TYPE))
1349 		return true;
1350 
1351 	return false;
1352 }
1353 
1354 
1355 /*! \brief Selects the text within the given offsets.
1356 	\param startOffset The offset of the text to select.
1357 	\param endOffset The offset where the text ends.
1358 */
1359 void
1360 BTextView::Select(int32 startOffset, int32 endOffset)
1361 {
1362 	CALLED();
1363 	if (!fSelectable)
1364 		return;
1365 
1366 	CancelInputMethod();
1367 
1368 	// a negative selection?
1369 	if (startOffset > endOffset)
1370 		return;
1371 
1372 	// is the new selection any different from the current selection?
1373 	if (startOffset == fSelStart && endOffset == fSelEnd)
1374 		return;
1375 
1376 	fStyles->InvalidateNullStyle();
1377 
1378 	// pin offsets at reasonable values
1379 	startOffset = (startOffset < 0) ? 0 : startOffset;
1380 	endOffset = (endOffset < 0) ? 0 : endOffset;
1381 	endOffset = (endOffset > fText->Length()) ? fText->Length() : endOffset;
1382 
1383 	// hide the caret
1384 	if (fCaretVisible)
1385 		InvertCaret();
1386 
1387 	if (startOffset == endOffset) {
1388 		if (fSelStart != fSelEnd) {
1389 			// unhilite the selection
1390 			if (fActive)
1391 				Highlight(fSelStart, fSelEnd);
1392 		}
1393 		fSelStart = fSelEnd = fClickOffset = startOffset;
1394 	} else {
1395 		if (fActive) {
1396 			// draw only those ranges that are different
1397 			long start, end;
1398 			if (startOffset != fSelStart) {
1399 				// start of selection has changed
1400 				if (startOffset > fSelStart) {
1401 					start = fSelStart;
1402 					end = startOffset;
1403 				} else {
1404 					start = startOffset;
1405 					end = fSelStart;
1406 				}
1407 				Highlight(start, end);
1408 			}
1409 
1410 			if (endOffset != fSelEnd) {
1411 				// end of selection has changed
1412 				if (endOffset > fSelEnd) {
1413 					start = fSelEnd;
1414 					end = endOffset;
1415 				} else {
1416 					start = endOffset;
1417 					end = fSelEnd;
1418 				}
1419 				Highlight(start, end);
1420 			}
1421 		}
1422 		fSelStart = startOffset;
1423 		fSelEnd = fClickOffset = endOffset;
1424 	}
1425 }
1426 
1427 
1428 /*! \brief Selects all the text within the BTextView.
1429 */
1430 void
1431 BTextView::SelectAll()
1432 {
1433 	Select(0, fText->Length());
1434 }
1435 
1436 
1437 /*! \brief Gets the current selection.
1438 	\param outStart A pointer to an int32 which will contain the selection start's offset.
1439 	\param outEnd A pointer to an int32 which will contain the selection end's offset.
1440 */
1441 void
1442 BTextView::GetSelection(int32 *outStart, int32 *outEnd) const
1443 {
1444 	int32 start = 0, end = 0;
1445 
1446 	if (fSelectable) {
1447 		start = fSelStart;
1448 		end = fSelEnd;
1449 	}
1450 
1451 	if (outStart)
1452 		*outStart = start;
1453 	if (outEnd)
1454 		*outEnd = end;
1455 }
1456 
1457 
1458 void
1459 BTextView::SetFontAndColor(const BFont *inFont, uint32 inMode, const rgb_color *inColor)
1460 {
1461 	SetFontAndColor(fSelStart, fSelEnd, inFont, inMode, inColor);
1462 }
1463 
1464 
1465 void
1466 BTextView::SetFontAndColor(int32 startOffset, int32 endOffset, const BFont* font,
1467 	uint32 fontMode, const rgb_color* color)
1468 {
1469 	CALLED();
1470 
1471 	if (startOffset > endOffset)
1472 		return;
1473 
1474 	BFont newFont;
1475 	if (font != NULL) {
1476 		newFont = font;
1477 		NormalizeFont(&newFont);
1478 	}
1479 
1480 	const int32 textLength = fText->Length();
1481 
1482 	if (!fStylable) {
1483 		// When the text view is not stylable, we always set the whole text's
1484 		// style and ignore the offsets
1485 		startOffset = 0;
1486 		endOffset = textLength;
1487 	}
1488 
1489 	// pin offsets at reasonable values
1490 	if (startOffset < 0)
1491 		startOffset = 0;
1492 	else if (startOffset > textLength)
1493 		startOffset = textLength;
1494 
1495 	if (endOffset < 0)
1496 		endOffset = 0;
1497 	else if (endOffset > textLength)
1498 		endOffset = textLength;
1499 
1500 	// add the style to the style buffer
1501 	fStyles->SetStyleRange(startOffset, endOffset, fText->Length(),
1502 		fontMode, &newFont, color);
1503 
1504 	if (fontMode & B_FONT_FAMILY_AND_STYLE || fontMode & B_FONT_SIZE) {
1505 		// recalc the line breaks and redraw with new style
1506 		Refresh(startOffset, endOffset, startOffset != endOffset, false);
1507 	} else {
1508 		// the line breaks wont change, simply redraw
1509 		DrawLines(LineAt(startOffset), LineAt(endOffset), startOffset, true);
1510 	}
1511 }
1512 
1513 
1514 void
1515 BTextView::GetFontAndColor(int32 inOffset, BFont *outFont, rgb_color *outColor) const
1516 {
1517 	fStyles->GetStyle(inOffset, outFont, outColor);
1518 }
1519 
1520 
1521 void
1522 BTextView::GetFontAndColor(BFont *outFont, uint32 *outMode, rgb_color *outColor, bool *outEqColor) const
1523 {
1524 	fStyles->ContinuousGetStyle(outFont, outMode, outColor, outEqColor, fSelStart, fSelEnd);
1525 }
1526 
1527 
1528 void
1529 BTextView::SetRunArray(int32 startOffset, int32 endOffset, const text_run_array *inRuns)
1530 {
1531 	CALLED();
1532 	if (startOffset > endOffset)
1533 		return;
1534 
1535 	CancelInputMethod();
1536 
1537 	const int32 textLength = fText->Length();
1538 
1539 	// pin offsets at reasonable values
1540 	if (startOffset < 0)
1541 		startOffset = 0;
1542 	else if (startOffset > textLength)
1543 		startOffset = textLength;
1544 
1545 	if (endOffset < 0)
1546 		endOffset = 0;
1547 	else if (endOffset > textLength)
1548 		endOffset = textLength;
1549 
1550 	long numStyles = inRuns->count;
1551 	if (numStyles > 0) {
1552 		const text_run *theRun = &inRuns->runs[0];
1553 		for (long index = 0; index < numStyles; index++) {
1554 			long fromOffset = theRun->offset + startOffset;
1555 			long toOffset = endOffset;
1556 			if (index + 1 < numStyles) {
1557 				toOffset = (theRun + 1)->offset + startOffset;
1558 				toOffset = (toOffset > endOffset) ? endOffset : toOffset;
1559 			}
1560 
1561 			fStyles->SetStyleRange(fromOffset, toOffset, textLength,
1562 							  	  B_FONT_ALL, &theRun->font, &theRun->color);
1563 
1564 			theRun++;
1565 		}
1566 		fStyles->InvalidateNullStyle();
1567 
1568 		Refresh(startOffset, endOffset, true, false);
1569 	}
1570 }
1571 
1572 
1573 /*! \brief Returns a RunArray for the text within the given offsets.
1574 	\param startOffset The offset where to start.
1575 	\param endOffset The offset where the wanted text ends.
1576 	\param outSize A pointer to an int32 which will contain the size
1577 		of the run array.
1578 	\return A text_run_array for the text in the given offsets.
1579 
1580 	The returned text_run_array belongs to the caller, so you better
1581 	free it as soon as you don't need it.
1582 */
1583 text_run_array *
1584 BTextView::RunArray(int32 startOffset, int32 endOffset, int32 *outSize) const
1585 {
1586 	STEStyleRange* styleRange = fStyles->GetStyleRange(startOffset, endOffset - 1);
1587 	if (styleRange == NULL)
1588 		return NULL;
1589 
1590 	text_run_array *runArray = AllocRunArray(styleRange->count, outSize);
1591 	if (runArray != NULL) {
1592 		for (int32 i = 0; i < runArray->count; i++) {
1593 			runArray->runs[i].offset = styleRange->runs[i].offset;
1594 			runArray->runs[i].font = styleRange->runs[i].style.font;
1595 			runArray->runs[i].color = styleRange->runs[i].style.color;
1596 		}
1597 	}
1598 
1599 	free(styleRange);
1600 
1601 	return runArray;
1602 }
1603 
1604 
1605 /*! \brief Returns the line number for the character at the given offset.
1606 	\param offset The offset of the wanted character.
1607 	\return A line number.
1608 */
1609 int32
1610 BTextView::LineAt(int32 offset) const
1611 {
1612 	return fLines->OffsetToLine(offset);
1613 }
1614 
1615 
1616 /*! \brief Returns the line number for the given point.
1617 	\param point A point.
1618 	\return A line number.
1619 */
1620 int32
1621 BTextView::LineAt(BPoint point) const
1622 {
1623 	return fLines->PixelToLine(point.y - fTextRect.top);
1624 }
1625 
1626 
1627 /*! \brief Returns the location of the character at the given offset.
1628 	\param inOffset The offset of the character.
1629 	\param outHeight Here the function will put the height of the character at the
1630 	given offset.
1631 	\return A BPoint which is the location of the character.
1632 */
1633 BPoint
1634 BTextView::PointAt(int32 inOffset, float *outHeight) const
1635 {
1636 	// TODO: Cleanup.
1637 	const int32 textLength = fText->Length();
1638 	int32 lineNum = LineAt(inOffset);
1639 	STELine* line = (*fLines)[lineNum];
1640 	float height = 0;
1641 
1642 	BPoint result;
1643 	result.x = 0.0;
1644 	result.y = line->origin + fTextRect.top;
1645 
1646 	// Handle the case where there is only one line
1647 	// (no text inserted)
1648 	// TODO: See if we can do this better
1649 	if (fStyles->NumRuns() == 0) {
1650 		const rgb_color *color = NULL;
1651 		const BFont *font = NULL;
1652 		fStyles->GetNullStyle(&font, &color);
1653 
1654 		font_height fontHeight;
1655 		font->GetHeight(&fontHeight);
1656 		height = fontHeight.ascent + fontHeight.descent;
1657 
1658 	} else {
1659 		height = (line + 1)->origin - line->origin;
1660 
1661 		// special case: go down one line if inOffset is a newline
1662 		if (inOffset == textLength && (*fText)[inOffset - 1] == B_ENTER) {
1663 			result.y += height;
1664 			height = LineHeight(CountLines() - 1);
1665 
1666 		} else {
1667 			int32 offset = line->offset;
1668 			int32 length = inOffset - line->offset;
1669 			int32 numBytes = length;
1670 			bool foundTab = false;
1671 			do {
1672 				foundTab = fText->FindChar(B_TAB, offset, &numBytes);
1673 
1674 				float width = StyledWidth(offset, numBytes);
1675 
1676 				result.x += width;
1677 
1678 				if (foundTab) {
1679 					result.x += ActualTabWidth(result.x);
1680 					numBytes++;
1681 				}
1682 
1683 				offset += numBytes;
1684 				length -= numBytes;
1685 				numBytes = length;
1686 			} while (foundTab && length > 0);
1687 		}
1688 	}
1689 
1690 	if (fAlignment != B_ALIGN_LEFT) {
1691 		float modifier = fTextRect.right - LineWidth(lineNum);
1692 		if (fAlignment == B_ALIGN_CENTER)
1693 			modifier /= 2;
1694 		result.x += modifier;
1695 	}
1696 	// convert from text rect coordinates
1697 	// NOTE: I didn't understand why "- 1.0"
1698 	// and it works only correct without it on Haiku app_server.
1699 	// Feel free to enlighten me though!
1700 	result.x += fTextRect.left;// - 1.0;
1701 
1702 	// round up
1703 	result.x = ceilf(result.x);
1704 	result.y = ceilf(result.y);
1705 	if (outHeight != NULL)
1706 		*outHeight = height;
1707 
1708 	return result;
1709 }
1710 
1711 
1712 /*! \brief Returns the offset for the given location.
1713 	\param point A BPoint which specify the wanted location.
1714 	\return The offset for the given point.
1715 */
1716 int32
1717 BTextView::OffsetAt(BPoint point) const
1718 {
1719 	const int32 textLength = fText->Length();
1720 
1721 	// should we even bother?
1722 	if (point.y >= fTextRect.bottom)
1723 		return textLength;
1724 	else if (point.y < fTextRect.top)
1725 		return 0;
1726 
1727 	int32 lineNum = LineAt(point);
1728 	STELine* line = (*fLines)[lineNum];
1729 
1730 	// special case: if point is within the text rect and PixelToLine()
1731 	// tells us that it's on the last line, but if point is actually
1732 	// lower than the bottom of the last line, return the last offset
1733 	// (can happen for newlines)
1734 	if (lineNum == (fLines->NumLines() - 1)) {
1735 		if (point.y >= ((line + 1)->origin + fTextRect.top))
1736 			return textLength;
1737 	}
1738 
1739 	// convert to text rect coordinates
1740 	if (fAlignment != B_ALIGN_LEFT) {
1741 		float lineWidth = fTextRect.right - LineWidth(lineNum);
1742 		if (fAlignment == B_ALIGN_CENTER)
1743 			lineWidth /= 2;
1744 		point.x -= lineWidth;
1745 	}
1746 
1747 	point.x -= fTextRect.left;
1748 	point.x = max_c(point.x, 0.0);
1749 
1750 	// TODO: The following code isn't very efficient because it always starts from the left end,
1751 	// so when the point is near the right end it's very slow.
1752 	int32 offset = line->offset;
1753 	const int32 limit = (line + 1)->offset;
1754 	float location = 0;
1755 	do {
1756 		const int32 nextInitial = NextInitialByte(offset);
1757 		const int32 saveOffset = offset;
1758 		float width = 0;
1759 		if (ByteAt(offset) == B_TAB)
1760 			width = ActualTabWidth(location);
1761 		else
1762 			width = StyledWidth(saveOffset, nextInitial - saveOffset);
1763 		if (location + width > point.x) {
1764 			if (fabs(location + width - point.x) < fabs(location - point.x))
1765 				offset = nextInitial;
1766 			break;
1767 		}
1768 
1769 		location += width;
1770 		offset = nextInitial;
1771 	} while (offset < limit);
1772 
1773 	if (offset == (line + 1)->offset) {
1774 		// special case: newlines aren't visible
1775 		// return the offset of the character preceding the newline
1776 		if (ByteAt(offset - 1) == B_ENTER)
1777 			return --offset;
1778 
1779 		// special case: return the offset preceding any spaces that
1780 		// aren't at the end of the buffer
1781 		if (offset != textLength && ByteAt(offset - 1) == B_SPACE)
1782 			return --offset;
1783 	}
1784 
1785 	return offset;
1786 }
1787 
1788 
1789 /*! \brief Returns the offset of the given line.
1790 	\param line A line number.
1791 	\return The offset of the passed line.
1792 */
1793 int32
1794 BTextView::OffsetAt(int32 line) const
1795 {
1796 	if (line > fLines->NumLines())
1797 		return fText->Length();
1798 
1799 	return (*fLines)[line]->offset;
1800 }
1801 
1802 
1803 /*! \brief Looks for a sequence of character that qualifies as a word.
1804 	\param inOffset The offset where to start looking.
1805 	\param outFromOffset A pointer to an integer which will contain the starting offset of the word.
1806 	\param outToOffset A pointer to an integer which will contain the ending offset of the word.
1807 */
1808 void
1809 BTextView::FindWord(int32 inOffset, int32 *outFromOffset, int32 *outToOffset)
1810 {
1811 	int32 offset;
1812 	uint32 charType = CharClassification(inOffset);
1813 
1814 	// check to the left
1815 	int32 previous;
1816 	for (offset = inOffset, previous = offset; offset > 0;
1817 				previous = PreviousInitialByte(offset)) {
1818 		if (CharClassification(previous) != charType)
1819 			break;
1820 		offset = previous;
1821 	}
1822 
1823 	if (outFromOffset)
1824 		*outFromOffset = offset;
1825 
1826 	// check to the right
1827 	int32 textLen = TextLength();
1828 	for (offset = inOffset; offset < textLen; offset = NextInitialByte(offset)) {
1829 		if (CharClassification(offset) != charType)
1830 			break;
1831 	}
1832 
1833 	if (outToOffset)
1834 		*outToOffset = offset;
1835 }
1836 
1837 
1838 /*! \brief Returns true if the character at the given offset can be the last character in a line.
1839 	\param offset The offset of the character.
1840 	\return true if the character can be the last of a line, false if not.
1841 */
1842 bool
1843 BTextView::CanEndLine(int32 offset)
1844 {
1845 	// TODO: Could be improved, the bebook says there are other checks to do
1846 	return (CharClassification(offset) == B_SEPARATOR_CHARACTER);
1847 }
1848 
1849 
1850 /*! \brief Returns the width of the line at the given index.
1851 	\param lineNum A line index.
1852 */
1853 float
1854 BTextView::LineWidth(int32 lineNum) const
1855 {
1856 	if (lineNum < 0 || lineNum >= fLines->NumLines())
1857 		return 0;
1858 
1859 	STELine* line = (*fLines)[lineNum];
1860 	return StyledWidth(line->offset, (line + 1)->offset - line->offset);
1861 }
1862 
1863 
1864 /*! \brief Returns the height of the line at the given index.
1865 	\param lineNum A line index.
1866 */
1867 float
1868 BTextView::LineHeight(int32 lineNum) const
1869 {
1870 	return TextHeight(lineNum, lineNum);
1871 }
1872 
1873 
1874 /*! \brief Returns the height of the text comprised between the two given lines.
1875 	\param startLine The index of the starting line.
1876 	\param endLine The index of the ending line.
1877 */
1878 float
1879 BTextView::TextHeight(int32 startLine, int32 endLine) const
1880 {
1881 	const int32 numLines = fLines->NumLines();
1882 	if (startLine < 0)
1883 		startLine = 0;
1884 	if (endLine > numLines - 1)
1885 		endLine = numLines - 1;
1886 
1887 	float height = (*fLines)[endLine + 1]->origin - (*fLines)[startLine]->origin;
1888 
1889 	if (startLine != endLine && endLine == numLines - 1 && (*fText)[fText->Length() - 1] == B_ENTER)
1890 		height += (*fLines)[endLine + 1]->origin - (*fLines)[endLine]->origin;
1891 
1892 	return ceilf(height);
1893 }
1894 
1895 
1896 void
1897 BTextView::GetTextRegion(int32 startOffset, int32 endOffset, BRegion *outRegion) const
1898 {
1899 	if (!outRegion)
1900 		return;
1901 
1902 	outRegion->MakeEmpty();
1903 
1904 	// return an empty region if the range is invalid
1905 	if (startOffset >= endOffset)
1906 		return;
1907 
1908 	float startLineHeight = 0.0;
1909 	float endLineHeight = 0.0;
1910 	BPoint startPt = PointAt(startOffset, &startLineHeight);
1911 	BPoint endPt = PointAt(endOffset, &endLineHeight);
1912 
1913 	startLineHeight = ceilf(startLineHeight);
1914 	endLineHeight = ceilf(endLineHeight);
1915 
1916 	BRect selRect;
1917 
1918 	if (startPt.y == endPt.y) {
1919 		// this is a one-line region
1920 		selRect.left = max_c(startPt.x, fTextRect.left);
1921 		selRect.top = startPt.y;
1922 		selRect.right = endPt.x - 1.0;
1923 		selRect.bottom = endPt.y + endLineHeight - 1.0;
1924 		outRegion->Include(selRect);
1925 	} else {
1926 		// more than one line in the specified offset range
1927 		selRect.left = max_c(startPt.x, fTextRect.left);
1928 		selRect.top = startPt.y;
1929 		selRect.right = fTextRect.right;
1930 		selRect.bottom = startPt.y + startLineHeight - 1.0;
1931 		outRegion->Include(selRect);
1932 
1933 		if (startPt.y + startLineHeight < endPt.y) {
1934 			// more than two lines in the range
1935 			selRect.left = fTextRect.left;
1936 			selRect.top = startPt.y + startLineHeight;
1937 			selRect.right = fTextRect.right;
1938 			selRect.bottom = endPt.y - 1.0;
1939 			outRegion->Include(selRect);
1940 		}
1941 
1942 		selRect.left = fTextRect.left;
1943 		selRect.top = endPt.y;
1944 		selRect.right = endPt.x - 1.0;
1945 		selRect.bottom = endPt.y + endLineHeight - 1.0;
1946 		outRegion->Include(selRect);
1947 	}
1948 }
1949 
1950 
1951 /*! \brief Scrolls the text so that the character at "inOffset" is within the visible range.
1952 	\param inOffset The offset of the character.
1953 */
1954 void
1955 BTextView::ScrollToOffset(int32 inOffset)
1956 {
1957 	BRect bounds = Bounds();
1958 	float lineHeight = 0.0;
1959         BPoint point = PointAt(inOffset, &lineHeight);
1960 
1961 	// TODO: We should do the following, since otherwise the textview
1962 	// won't scroll unless it's attached to a scrollview.
1963 	/*if (!bounds.Contains(point))
1964 		ScrollTo(point); */
1965 
1966 	if (ScrollBar(B_HORIZONTAL) != NULL) {
1967 		if (point.x < bounds.left || point.x >= bounds.right)
1968 			ScrollBar(B_HORIZONTAL)->SetValue(point.x - (bounds.IntegerWidth() / 2));
1969 	}
1970 
1971 	if (ScrollBar(B_VERTICAL) != NULL) {
1972 		if (point.y < bounds.top || (point.y + lineHeight) >= bounds.bottom)
1973 			ScrollBar(B_VERTICAL)->SetValue(point.y - (bounds.IntegerHeight() / 2));
1974 	}
1975 }
1976 
1977 
1978 /*! \brief Scrolls the text so that the character which begins the current selection
1979 		is within the visible range.
1980 	\param inOffset The offset of the character.
1981 */
1982 void
1983 BTextView::ScrollToSelection()
1984 {
1985 	ScrollToOffset(fSelStart);
1986 }
1987 
1988 
1989 /*! \brief Highlight the text comprised between the given offset.
1990 	\param startOffset The offset of the text to highlight.
1991 	\param endOffset The offset where the text to highlight ends.
1992 */
1993 void
1994 BTextView::Highlight(int32 startOffset, int32 endOffset)
1995 {
1996 	// get real
1997 	if (startOffset >= endOffset)
1998 		return;
1999 
2000 	BRegion selRegion;
2001 	GetTextRegion(startOffset, endOffset, &selRegion);
2002 
2003 	SetDrawingMode(B_OP_INVERT);
2004 	FillRegion(&selRegion, B_SOLID_HIGH);
2005 	SetDrawingMode(B_OP_COPY);
2006 }
2007 
2008 
2009 /*! \brief Sets the BTextView's text rectangle to be the same as the passed rect.
2010 	\param rect A BRect.
2011 */
2012 void
2013 BTextView::SetTextRect(BRect rect)
2014 {
2015 	if (rect == fTextRect)
2016 		return;
2017 
2018 	fTextRect = rect;
2019 
2020 	if (Window() != NULL) {
2021 		Invalidate();
2022 		Window()->UpdateIfNeeded();
2023 	}
2024 }
2025 
2026 
2027 /*! \brief Returns the current BTextView's text rectangle.
2028 	\return The current text rectangle.
2029 */
2030 BRect
2031 BTextView::TextRect() const
2032 {
2033 	return fTextRect;
2034 }
2035 
2036 
2037 /*! \brief Sets whether the BTextView accepts multiple character styles.
2038 */
2039 void
2040 BTextView::SetStylable(bool stylable)
2041 {
2042 	fStylable = stylable;
2043 }
2044 
2045 
2046 /*! \brief Tells if the object is stylable.
2047 	\return true if the object is stylable, false otherwise.
2048 	If the object is stylable, it can show multiple fonts at the same time.
2049 */
2050 bool
2051 BTextView::IsStylable() const
2052 {
2053 	return fStylable;
2054 }
2055 
2056 
2057 /*! \brief Sets the distance between tab stops (in pixel).
2058 	\param width The distance (in pixel) between tab stops.
2059 */
2060 void
2061 BTextView::SetTabWidth(float width)
2062 {
2063 	if (width == fTabWidth)
2064 		return;
2065 
2066 	fTabWidth = width;
2067 
2068 	if (Window() != NULL)
2069 		Refresh(0, fText->Length(), true, false);
2070 }
2071 
2072 
2073 /*! \brief Returns the BTextView's tab width.
2074 	\return The BTextView's tab width.
2075 */
2076 float
2077 BTextView::TabWidth() const
2078 {
2079 	return fTabWidth;
2080 }
2081 
2082 
2083 /*! \brief Makes the object selectable, or not selectable.
2084 	\param selectable If true, the object will be selectable from now on.
2085 	 if false, it won't be selectable anymore.
2086 */
2087 void
2088 BTextView::MakeSelectable(bool selectable)
2089 {
2090 	if (selectable == fSelectable)
2091 		return;
2092 
2093 	fSelectable = selectable;
2094 
2095 	if (Window() != NULL) {
2096 		if (fActive) {
2097 			// show/hide the caret, hilite/unhilite the selection
2098 			if (fSelStart != fSelEnd)
2099 				Highlight(fSelStart, fSelEnd);
2100 			else
2101 				InvertCaret();
2102 		}
2103 	}
2104 }
2105 
2106 
2107 /*! \brief Tells if the object is selectable
2108 	\return \c true if the object is selectable,
2109 			\c false if not.
2110 */
2111 bool
2112 BTextView::IsSelectable() const
2113 {
2114 	return fSelectable;
2115 }
2116 
2117 
2118 /*! \brief Set (or remove) the editable property for the object.
2119 	\param editable If true, will make the object editable,
2120 		if false, will make it not editable.
2121 */
2122 void
2123 BTextView::MakeEditable(bool editable)
2124 {
2125 	if (editable == fEditable)
2126 		return;
2127 
2128 	fEditable = editable;
2129 	// TextControls change the color of the text when
2130 	// they are made editable, so we need to invalidate
2131 	// the NULL style here
2132 	// TODO: it works well, but it could be caused by a bug somewhere else
2133 	if (fEditable)
2134 		fStyles->InvalidateNullStyle();
2135 	if (Window() != NULL && fActive) {
2136 		if (!fEditable) {
2137 			if (fCaretVisible)
2138 				InvertCaret();
2139 			CancelInputMethod();
2140 		}
2141 	}
2142 }
2143 
2144 
2145 /*! \brief Tells if the object is editable.
2146 	\return \c true if the object is editable,
2147 			\c false if not.
2148 */
2149 bool
2150 BTextView::IsEditable() const
2151 {
2152 	return fEditable;
2153 }
2154 
2155 
2156 /*! \brief Set (or unset) word wrapping mode.
2157 	\param wrap Specifies if you want word wrapping active or not.
2158 */
2159 void
2160 BTextView::SetWordWrap(bool wrap)
2161 {
2162 	if (wrap == fWrap)
2163 		return;
2164 
2165 	if (Window() != NULL) {
2166 		if (fActive) {
2167 			// hide the caret, unhilite the selection
2168 			if (fSelStart != fSelEnd)
2169 				Highlight(fSelStart, fSelEnd);
2170 			else {
2171 				if (fCaretVisible)
2172 					InvertCaret();
2173 			}
2174 		}
2175 
2176 		fWrap = wrap;
2177 		Refresh(0, fText->Length(), true, true);
2178 
2179 		if (fActive) {
2180 			// show the caret, hilite the selection
2181 			if (fSelStart != fSelEnd && fSelectable)
2182 				Highlight(fSelStart, fSelEnd);
2183 			else {
2184 				if (!fCaretVisible)
2185 					InvertCaret();
2186 			}
2187 		}
2188 	}
2189 }
2190 
2191 
2192 /*! \brief Tells if word wrapping is activated.
2193 	\return true if word wrapping is active, false otherwise.
2194 */
2195 bool
2196 BTextView::DoesWordWrap() const
2197 {
2198 	return fWrap;
2199 }
2200 
2201 
2202 /*! \brief Sets the maximun number of bytes that the BTextView can contain.
2203 	\param max The new max number of bytes.
2204 */
2205 void
2206 BTextView::SetMaxBytes(int32 max)
2207 {
2208 	const int32 textLength = fText->Length();
2209 	fMaxBytes = max;
2210 
2211 	if (fMaxBytes < textLength)
2212 		Delete(fMaxBytes, textLength);
2213 }
2214 
2215 
2216 /*! \brief Returns the maximum number of bytes that the BTextView can contain.
2217 	\return the maximum number of bytes that the BTextView can contain.
2218 */
2219 int32
2220 BTextView::MaxBytes() const
2221 {
2222 	return fMaxBytes;
2223 }
2224 
2225 
2226 /*! \brief Adds the given char to the disallowed chars list.
2227 	\param aChar The character to add to the list.
2228 
2229 	After this function returns, the given character won't be accepted
2230 	by the textview anymore.
2231 */
2232 void
2233 BTextView::DisallowChar(uint32 aChar)
2234 {
2235 	if (fDisallowedChars == NULL)
2236 		fDisallowedChars = new BList;
2237 	if (!fDisallowedChars->HasItem(reinterpret_cast<void *>(aChar)))
2238 		fDisallowedChars->AddItem(reinterpret_cast<void *>(aChar));
2239 }
2240 
2241 
2242 /*! \brief Removes the given character from the disallowed list.
2243 	\param aChar The character to remove from the list.
2244 */
2245 void
2246 BTextView::AllowChar(uint32 aChar)
2247 {
2248 	if (fDisallowedChars != NULL)
2249 		fDisallowedChars->RemoveItem(reinterpret_cast<void *>(aChar));
2250 }
2251 
2252 
2253 /*! \brief Sets the way text is aligned within the text rectangle.
2254 	\param flag The new alignment.
2255 */
2256 void
2257 BTextView::SetAlignment(alignment flag)
2258 {
2259 	// Do a reality check
2260 	if (fAlignment != flag &&
2261 			(flag == B_ALIGN_LEFT ||
2262 			 flag == B_ALIGN_RIGHT ||
2263 			 flag == B_ALIGN_CENTER)) {
2264 		fAlignment = flag;
2265 
2266 		// After setting new alignment, update the view/window
2267 		BWindow *window = Window();
2268 		if (window) {
2269 			Invalidate();
2270 			window->UpdateIfNeeded();
2271 		}
2272 	}
2273 }
2274 
2275 
2276 /*! \brief Returns the current alignment of the text.
2277 	\return The current alignment.
2278 */
2279 alignment
2280 BTextView::Alignment() const
2281 {
2282 	return fAlignment;
2283 }
2284 
2285 
2286 /*! \brief Sets wheter a new line of text is automatically indented.
2287 	\param state The new autoindent state
2288 */
2289 void
2290 BTextView::SetAutoindent(bool state)
2291 {
2292 	fAutoindent = state;
2293 }
2294 
2295 
2296 /*! \brief Returns the current autoindent state.
2297 	\return The current autoindent state.
2298 */
2299 bool
2300 BTextView::DoesAutoindent() const
2301 {
2302 	return fAutoindent;
2303 }
2304 
2305 
2306 /*! \brief Set the color space for the offscreen BBitmap.
2307 	\param colors The new colorspace for the offscreen BBitmap.
2308 */
2309 void
2310 BTextView::SetColorSpace(color_space colors)
2311 {
2312 	if (colors != fColorSpace && fOffscreen) {
2313 		fColorSpace = colors;
2314 		DeleteOffscreen();
2315 		NewOffscreen();
2316 	}
2317 }
2318 
2319 
2320 /*! \brief Returns the colorspace of the offscreen BBitmap, if any.
2321 	\return The colorspace of the BTextView's offscreen BBitmap.
2322 */
2323 color_space
2324 BTextView::ColorSpace() const
2325 {
2326 	return fColorSpace;
2327 }
2328 
2329 
2330 /*! \brief Gives to the BTextView the ability to automatically resize itself when needed.
2331 	\param resize If true, the BTextView will automatically resize itself.
2332 	\param resizeView The BTextView's parent view, it's the view which resizes itself.
2333 	The resizing mechanism is alternative to the BView resizing. The container view
2334 	(the one passed to this function) should not automatically resize itself when the parent is
2335 	resized.
2336 */
2337 void
2338 BTextView::MakeResizable(bool resize, BView *resizeView)
2339 {
2340 	if (resize) {
2341 		fResizable = true;
2342 		fContainerView = resizeView;
2343 
2344 		// Wrapping mode and resizable mode can't live together
2345 		if (fWrap) {
2346 			fWrap = false;
2347 
2348 			if (fActive && Window() != NULL) {
2349 				if (fSelStart != fSelEnd && fSelectable)
2350 					Highlight(fSelStart, fSelEnd);
2351 
2352 				else if (fCaretVisible)
2353 					InvertCaret();
2354 			}
2355 		}
2356 	} else {
2357 		fResizable = false;
2358 		fContainerView = NULL;
2359 		if (fOffscreen)
2360 			DeleteOffscreen();
2361 		NewOffscreen();
2362 	}
2363 
2364 	Refresh(0, fText->Length(), true, false);
2365 }
2366 
2367 
2368 /*! \brief Returns whether the BTextView is currently resizable.
2369 	\returns whether the BTextView is currently resizable.
2370 */
2371 bool
2372 BTextView::IsResizable() const
2373 {
2374 	return fResizable;
2375 }
2376 
2377 
2378 /*! \brief Enables or disables the undo mechanism.
2379 	\param undo If true enables the undo mechanism, if false, disables it.
2380 */
2381 void
2382 BTextView::SetDoesUndo(bool undo)
2383 {
2384 	if (undo && fUndo == NULL)
2385 		fUndo = new _BUndoBuffer_(this, B_UNDO_UNAVAILABLE);
2386 	else if (!undo && fUndo != NULL) {
2387 		delete fUndo;
2388 		fUndo = NULL;
2389 	}
2390 }
2391 
2392 
2393 /*! \brief Tells if the object is undoable.
2394 	\return Whether the object is undoable.
2395 */
2396 bool
2397 BTextView::DoesUndo() const
2398 {
2399 	return fUndo != NULL;
2400 }
2401 
2402 
2403 void
2404 BTextView::HideTyping(bool enabled)
2405 {
2406 	if (enabled)
2407 		Delete(0, fText->Length());
2408 
2409 	fText->SetPasswordMode(enabled);
2410 }
2411 
2412 
2413 bool
2414 BTextView::IsTypingHidden() const
2415 {
2416 	return fText->PasswordMode();
2417 }
2418 
2419 
2420 void
2421 BTextView::ResizeToPreferred()
2422 {
2423 	float widht, height;
2424 	GetPreferredSize(&widht, &height);
2425 	BView::ResizeTo(widht, height);
2426 }
2427 
2428 
2429 void
2430 BTextView::GetPreferredSize(float *width, float *height)
2431 {
2432 	BView::GetPreferredSize(width, height);
2433 }
2434 
2435 
2436 void
2437 BTextView::AllAttached()
2438 {
2439 	BView::AllAttached();
2440 }
2441 
2442 
2443 void
2444 BTextView::AllDetached()
2445 {
2446 	BView::AllDetached();
2447 }
2448 
2449 
2450 /* static */
2451 text_run_array *
2452 BTextView::AllocRunArray(int32 entryCount, int32 *outSize)
2453 {
2454 	int32 size = sizeof(text_run_array) + (entryCount - 1) * sizeof(text_run);
2455 
2456 	text_run_array *runArray = (text_run_array *)malloc(size);
2457 	if (runArray == NULL) {
2458 		if (outSize != NULL)
2459 			*outSize = 0;
2460 		return NULL;
2461 	}
2462 
2463 	memset(runArray, 0, sizeof(size));
2464 
2465 	runArray->count = entryCount;
2466 
2467 	// Call constructors explicitly as the text_run_array
2468 	// was allocated with malloc (and has to, for backwards
2469 	// compatibility)
2470 	for (int32 i = 0; i < runArray->count; i++) {
2471 		new (&runArray->runs[i].font) BFont;
2472 	}
2473 
2474 	if (outSize != NULL)
2475 		*outSize = size;
2476 
2477 	return runArray;
2478 }
2479 
2480 
2481 /* static */
2482 text_run_array *
2483 BTextView::CopyRunArray(const text_run_array *orig, int32 countDelta)
2484 {
2485 	text_run_array *copy = AllocRunArray(countDelta, NULL);
2486 	if (copy != NULL) {
2487 		for (int32 i = 0; i < countDelta; i++) {
2488 			copy->runs[i].offset = orig->runs[i].offset;
2489 			copy->runs[i].font = orig->runs[i].font;
2490 			copy->runs[i].color = orig->runs[i].color;
2491 		}
2492 	}
2493 	return copy;
2494 }
2495 
2496 
2497 /* static */
2498 void
2499 BTextView::FreeRunArray(text_run_array *array)
2500 {
2501 	if (array == NULL)
2502 		return;
2503 
2504 	// Call destructors explicitly
2505 	for (int32 i = 0; i < array->count; i++)
2506 		array->runs[i].font.~BFont();
2507 
2508 	free(array);
2509 }
2510 
2511 
2512 /* static */
2513 void *
2514 BTextView::FlattenRunArray(const text_run_array* runArray, int32* _size)
2515 {
2516 	CALLED();
2517 	int32 size = sizeof(flattened_text_run_array) + (runArray->count - 1)
2518 		* sizeof(flattened_text_run);
2519 
2520 	flattened_text_run_array *array = (flattened_text_run_array *)malloc(size);
2521 	if (array == NULL) {
2522 		if (_size)
2523 			*_size = 0;
2524 		return NULL;
2525 	}
2526 
2527 	array->magic = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayMagic);
2528 	array->version = B_HOST_TO_BENDIAN_INT32(kFlattenedTextRunArrayVersion);
2529 	array->count = B_HOST_TO_BENDIAN_INT32(runArray->count);
2530 
2531 	for (int32 i = 0; i < runArray->count; i++) {
2532 		array->styles[i].offset = B_HOST_TO_BENDIAN_INT32(runArray->runs[i].offset);
2533 		runArray->runs[i].font.GetFamilyAndStyle(&array->styles[i].family,
2534 			&array->styles[i].style);
2535 		array->styles[i].size = B_HOST_TO_BENDIAN_FLOAT(runArray->runs[i].font.Size());
2536 		array->styles[i].shear = B_HOST_TO_BENDIAN_FLOAT(runArray->runs[i].font.Shear());
2537 		array->styles[i].face = B_HOST_TO_BENDIAN_INT16(runArray->runs[i].font.Face());
2538 		array->styles[i].red = runArray->runs[i].color.red;
2539 		array->styles[i].green = runArray->runs[i].color.green;
2540 		array->styles[i].blue = runArray->runs[i].color.blue;
2541 		array->styles[i].alpha = 255;
2542 		array->styles[i]._reserved_ = 0;
2543 	}
2544 
2545 	if (_size)
2546 		*_size = size;
2547 
2548 	return array;
2549 }
2550 
2551 
2552 /* static */
2553 text_run_array *
2554 BTextView::UnflattenRunArray(const void* data, int32* _size)
2555 {
2556 	CALLED();
2557 	flattened_text_run_array *array = (flattened_text_run_array *)data;
2558 
2559 	if (B_BENDIAN_TO_HOST_INT32(array->magic) != kFlattenedTextRunArrayMagic
2560 		|| B_BENDIAN_TO_HOST_INT32(array->version) != kFlattenedTextRunArrayVersion) {
2561 		if (_size)
2562 			*_size = 0;
2563 
2564 		return NULL;
2565 	}
2566 
2567 	int32 count = B_BENDIAN_TO_HOST_INT32(array->count);
2568 
2569 	text_run_array *runArray = AllocRunArray(count, _size);
2570 	if (runArray == NULL)
2571 		return NULL;
2572 
2573 	for (int32 i = 0; i < count; i++) {
2574 		runArray->runs[i].offset = B_BENDIAN_TO_HOST_INT32(array->styles[i].offset);
2575 
2576 		// Set family and style independently from each other, so that
2577 		// even if the family doesn't exist, we try to preserve the style
2578 		runArray->runs[i].font.SetFamilyAndStyle(array->styles[i].family, NULL);
2579 		runArray->runs[i].font.SetFamilyAndStyle(NULL, array->styles[i].style);
2580 
2581 		runArray->runs[i].font.SetSize(B_BENDIAN_TO_HOST_FLOAT(array->styles[i].size));
2582 		runArray->runs[i].font.SetShear(B_BENDIAN_TO_HOST_FLOAT(array->styles[i].shear));
2583 
2584 		uint16 face = B_BENDIAN_TO_HOST_INT16(array->styles[i].face);
2585 		if (face != B_REGULAR_FACE) {
2586 			// Be's version doesn't seem to set this correctly
2587 			runArray->runs[i].font.SetFace(face);
2588 		}
2589 
2590 		runArray->runs[i].color.red = array->styles[i].red;
2591 		runArray->runs[i].color.green = array->styles[i].green;
2592 		runArray->runs[i].color.blue = array->styles[i].blue;
2593 		runArray->runs[i].color.alpha = array->styles[i].alpha;
2594 	}
2595 
2596 	return runArray;
2597 }
2598 
2599 
2600 void
2601 BTextView::InsertText(const char *inText, int32 inLength, int32 inOffset,
2602 						   const text_run_array *inRuns)
2603 {
2604 	CALLED();
2605 	// why add nothing?
2606 	if (inLength < 1)
2607 		return;
2608 
2609 	// TODO: Pin offset/lenght
2610 	// add the text to the buffer
2611 	fText->InsertText(inText, inLength, inOffset);
2612 
2613 	// update the start offsets of each line below offset
2614 	fLines->BumpOffset(inLength, LineAt(inOffset) + 1);
2615 
2616 	// update the style runs
2617 	fStyles->BumpOffset(inLength, fStyles->OffsetToRun(inOffset - 1) + 1);
2618 
2619 	if (inRuns != NULL)
2620 		SetRunArray(inOffset, inOffset + inLength, inRuns);
2621 	else {
2622 		// apply nullStyle to inserted text
2623 		fStyles->SyncNullStyle(inOffset);
2624 		fStyles->SetStyleRange(inOffset, inOffset + inLength,
2625 							  fText->Length(), B_FONT_ALL, NULL, NULL);
2626 	}
2627 }
2628 
2629 
2630 void
2631 BTextView::DeleteText(int32 fromOffset, int32 toOffset)
2632 {
2633 	CALLED();
2634 	// sanity checking
2635 	if (fromOffset >= toOffset || fromOffset < 0 || toOffset > fText->Length())
2636 		return;
2637 
2638 	// set nullStyle to style at beginning of range
2639 	fStyles->InvalidateNullStyle();
2640 	fStyles->SyncNullStyle(fromOffset);
2641 
2642 	// remove from the text buffer
2643 	fText->RemoveRange(fromOffset, toOffset);
2644 
2645 	// remove any lines that have been obliterated
2646 	fLines->RemoveLineRange(fromOffset, toOffset);
2647 
2648 	// remove any style runs that have been obliterated
2649 	fStyles->RemoveStyleRange(fromOffset, toOffset);
2650 }
2651 
2652 
2653 /*! \brief Undoes the last changes.
2654 	\param clipboard A clipboard to use for the undo operation.
2655 */
2656 void
2657 BTextView::Undo(BClipboard *clipboard)
2658 {
2659 	if (fUndo)
2660 		fUndo->Undo(clipboard);
2661 }
2662 
2663 
2664 undo_state
2665 BTextView::UndoState(bool *isRedo) const
2666 {
2667 	return fUndo == NULL ? B_UNDO_UNAVAILABLE : fUndo->State(isRedo);
2668 }
2669 
2670 
2671 void
2672 BTextView::GetDragParameters(BMessage *drag, BBitmap **bitmap, BPoint *point, BHandler **handler)
2673 {
2674 	CALLED();
2675 	if (drag == NULL)
2676 		return;
2677 
2678 	// Add originator and action
2679 	drag->AddPointer("be:originator", this);
2680 	drag->AddInt32("be_actions", B_TRASH_TARGET);
2681 
2682 	// add the text
2683 	drag->AddData("text/plain", B_MIME_TYPE, fText->Text() + fSelStart,
2684 			  	  fSelEnd - fSelStart);
2685 
2686 	// add the corresponding styles
2687 	int32 size = 0;
2688 	text_run_array *styles = RunArray(fSelStart, fSelEnd, &size);
2689 
2690 	if (styles != NULL) {
2691 		drag->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
2692 			styles, size);
2693 
2694 		FreeRunArray(styles);
2695 	}
2696 
2697 	if (bitmap != NULL)
2698 		*bitmap = NULL;
2699 	if (handler != NULL)
2700 		*handler = NULL;
2701 }
2702 
2703 
2704 void BTextView::_ReservedTextView3() {}
2705 void BTextView::_ReservedTextView4() {}
2706 void BTextView::_ReservedTextView5() {}
2707 void BTextView::_ReservedTextView6() {}
2708 void BTextView::_ReservedTextView7() {}
2709 void BTextView::_ReservedTextView8() {}
2710 void BTextView::_ReservedTextView9() {}
2711 void BTextView::_ReservedTextView10() {}
2712 void BTextView::_ReservedTextView11() {}
2713 void BTextView::_ReservedTextView12() {}
2714 
2715 
2716 /*! \brief Inits the BTextView object.
2717 	\param textRect The BTextView's text rect.
2718 	\param initialFont The font which the BTextView will use.
2719 	\param initialColor The initial color of the text.
2720 */
2721 void
2722 BTextView::InitObject(BRect textRect, const BFont *initialFont,
2723 						   const rgb_color *initialColor)
2724 {
2725 	BFont font;
2726 	if (initialFont == NULL)
2727 		GetFont(&font);
2728 	else
2729 		font = *initialFont;
2730 
2731 	NormalizeFont(&font);
2732 
2733 	if (initialColor == NULL)
2734 		initialColor = &kBlackColor;
2735 
2736 	fText = new _BTextGapBuffer_;
2737 	fLines = new _BLineBuffer_;
2738 	fStyles = new _BStyleBuffer_(&font, initialColor);
2739 
2740 	// We put these here instead of in the constructor initializer list
2741 	// to have less code duplication, and a single place where to do changes
2742 	// if needed.,
2743 	fTextRect = textRect;
2744 	fSelStart = fSelEnd = 0;
2745 	fCaretVisible = false;
2746 	fCaretTime = 0;
2747 	fClickOffset = 0;
2748 	fClickCount = 0;
2749 	fClickTime = 0;
2750 	fDragOffset = -1;
2751 	fCursor = 0;
2752 	fActive = false;
2753 	fStylable = false;
2754 	fTabWidth = 28.0;
2755 	fSelectable = true;
2756 	fEditable = true;
2757 	fWrap = true;
2758 	fMaxBytes = LONG_MAX;
2759 	fDisallowedChars = NULL;
2760 	fAlignment = B_ALIGN_LEFT;
2761 	fAutoindent = false;
2762 	fOffscreen = NULL;
2763 	fColorSpace = B_CMAP8;
2764 	fResizable = false;
2765 	fContainerView = NULL;
2766 	fUndo = NULL;
2767 	fInline = NULL;
2768 	fDragRunner = NULL;
2769 	fClickRunner = NULL;
2770 	fTrackingMouse = NULL;
2771 	fTextChange = NULL;
2772 }
2773 
2774 
2775 /*! \brief Called when Backspace key is pressed.
2776 */
2777 void
2778 BTextView::HandleBackspace()
2779 {
2780 	if (fUndo) {
2781 		_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo);
2782 		if (!undoBuffer) {
2783 			delete fUndo;
2784 			fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
2785 		}
2786 		undoBuffer->BackwardErase();
2787 	}
2788 
2789 	if (fSelStart == fSelEnd) {
2790 		if (fSelStart == 0)
2791 			return;
2792 		else
2793 			fSelStart = PreviousInitialByte(fSelStart);
2794 	} else
2795 		Highlight(fSelStart, fSelEnd);
2796 
2797 	DeleteText(fSelStart, fSelEnd);
2798 	fClickOffset = fSelEnd = fSelStart;
2799 
2800 	Refresh(fSelStart, fSelEnd, true, true);
2801 }
2802 
2803 
2804 /*! \brief Called when any arrow key is pressed.
2805 	\param inArrowKey The code for the pressed key.
2806 */
2807 void
2808 BTextView::HandleArrowKey(uint32 inArrowKey)
2809 {
2810 	// return if there's nowhere to go
2811 	if (fText->Length() == 0)
2812 		return;
2813 
2814 	int32 selStart = fSelStart;
2815 	int32 selEnd = fSelEnd;
2816 
2817 	int32 modifiers = 0;
2818 	BMessage *message = Window()->CurrentMessage();
2819 	if (message != NULL)
2820 		message->FindInt32("modifiers", &modifiers);
2821 
2822 	bool shiftDown = modifiers & B_SHIFT_KEY;
2823 
2824 	int32 currentOffset = fClickOffset;
2825 	switch (inArrowKey) {
2826 		case B_LEFT_ARROW:
2827 			if (shiftDown) {
2828 				fClickOffset = PreviousInitialByte(fClickOffset);
2829 				if (fClickOffset != currentOffset) {
2830 					if (fClickOffset >= fSelStart)
2831 						selEnd = fClickOffset;
2832 					else
2833 						selStart = fClickOffset;
2834 				}
2835 			} else if (fSelStart != fSelEnd)
2836 				fClickOffset = fSelStart;
2837 			else
2838 				fClickOffset = PreviousInitialByte(fSelStart);
2839 
2840 			break;
2841 
2842 		case B_RIGHT_ARROW:
2843 			if (shiftDown) {
2844 				fClickOffset = NextInitialByte(fClickOffset);
2845 				if (fClickOffset != currentOffset) {
2846 					if (fClickOffset <= fSelEnd)
2847 						selStart = fClickOffset;
2848 					else
2849 						selEnd = fClickOffset;
2850 				}
2851 			} else if (fSelStart != fSelEnd)
2852 				fClickOffset = fSelEnd;
2853 			else
2854 				fClickOffset = NextInitialByte(fSelEnd);
2855 			break;
2856 
2857 		case B_UP_ARROW:
2858 		{
2859 			float height;
2860 			BPoint point = PointAt(fClickOffset, &height);
2861 			point.y -= height;
2862 			fClickOffset = OffsetAt(point);
2863 			if (shiftDown) {
2864 				if (fClickOffset != currentOffset) {
2865 					if (fClickOffset >= fSelStart)
2866 						selEnd = fClickOffset;
2867 					else
2868 						selStart = fClickOffset;
2869 				}
2870 			}
2871 			break;
2872 		}
2873 
2874 		case B_DOWN_ARROW:
2875 		{
2876 			float height;
2877 			BPoint point = PointAt(fClickOffset, &height);
2878 			point.y += height;
2879 			fClickOffset = OffsetAt(point);
2880 			if (shiftDown) {
2881 				if (fClickOffset != currentOffset) {
2882 					if (fClickOffset <= fSelEnd)
2883 						selStart = fClickOffset;
2884 					else
2885 						selEnd = fClickOffset;
2886 				}
2887 			}
2888 			break;
2889 		}
2890 	}
2891 
2892 	// invalidate the null style
2893 	fStyles->InvalidateNullStyle();
2894 
2895 	currentOffset = fClickOffset;
2896 	if (shiftDown)
2897 		Select(selStart, selEnd);
2898 	else
2899 		Select(fClickOffset, fClickOffset);
2900 
2901 	fClickOffset = currentOffset;
2902 		// Select sets fClickOffset = fSelEnd
2903 
2904 	// scroll if needed
2905 	ScrollToOffset(fClickOffset);
2906 }
2907 
2908 
2909 /*! \brief Called when the Delete key is pressed.
2910 */
2911 void
2912 BTextView::HandleDelete()
2913 {
2914 	if (fUndo) {
2915 		_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo);
2916 		if (!undoBuffer) {
2917 			delete fUndo;
2918 			fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
2919 		}
2920 		undoBuffer->ForwardErase();
2921 	}
2922 
2923 	if (fSelStart == fSelEnd) {
2924 		if (fSelEnd == fText->Length())
2925 			return;
2926 		else
2927 			fSelEnd = NextInitialByte(fSelEnd);
2928 	} else
2929 		Highlight(fSelStart, fSelEnd);
2930 
2931 	DeleteText(fSelStart, fSelEnd);
2932 
2933 	fClickOffset = fSelEnd = fSelStart;
2934 
2935 	Refresh(fSelStart, fSelEnd, true, true);
2936 }
2937 
2938 
2939 /*! \brief Called when a "Page key" is pressed.
2940 	\param inPageKey The page key which has been pressed.
2941 */
2942 void
2943 BTextView::HandlePageKey(uint32 inPageKey)
2944 {
2945 	int32 mods = 0;
2946 	BMessage *currentMessage = Window()->CurrentMessage();
2947 	if (currentMessage)
2948 		currentMessage->FindInt32("modifiers", &mods);
2949 
2950 	bool shiftDown = mods & B_SHIFT_KEY;
2951 
2952 	STELine* line = NULL;
2953 
2954 	int32 start = fSelStart, end = fSelEnd;
2955 
2956 	switch (inPageKey) {
2957 		case B_HOME:
2958 			line = (*fLines)[CurrentLine()];
2959 			fClickOffset = line->offset;
2960 			if (shiftDown) {
2961 				if (fClickOffset <= fSelStart) {
2962 					start = fClickOffset;
2963 					end = fSelEnd;
2964 				} else {
2965 					start = fSelStart;
2966 					end = fClickOffset;
2967 				}
2968 			} else
2969 				start = end = fClickOffset;
2970 
2971 			break;
2972 
2973 		case B_END:
2974 			// If we are on the last line, just go to the last
2975 			// character in the buffer, otherwise get the starting
2976 			// offset of the next line, and go to the previous character
2977 			if (CurrentLine() + 1 < fLines->NumLines()) {
2978 				line = (*fLines)[CurrentLine() + 1];
2979 				fClickOffset = PreviousInitialByte(line->offset);
2980 			} else {
2981 				// This check if needed to avoid moving the cursor
2982 				// when the cursor is on the last line, and that line
2983 				// is empty
2984 				if (fClickOffset != fText->Length()) {
2985 					fClickOffset = fText->Length();
2986 					if (ByteAt(fClickOffset - 1) == B_ENTER)
2987 						fClickOffset--;
2988 				}
2989 			}
2990 
2991 			if (shiftDown) {
2992 				if (fClickOffset >= fSelEnd) {
2993 					start = fSelStart;
2994 					end = fClickOffset;
2995 				} else {
2996 					start = fClickOffset;
2997 					end = fSelEnd;
2998 				}
2999 			} else
3000 				start = end = fClickOffset;
3001 
3002 			break;
3003 
3004 		case B_PAGE_UP:
3005 		{
3006 			BPoint currentPos = PointAt(fClickOffset);
3007 
3008 			currentPos.y -= Bounds().Height();
3009 			fClickOffset = OffsetAt(LineAt(currentPos));
3010 
3011 			if (shiftDown) {
3012 				if (fClickOffset <= fSelStart) {
3013 					start = fClickOffset;
3014 					end = fSelEnd;
3015 				} else {
3016 					start = fSelStart;
3017 					end = fClickOffset;
3018 				}
3019 			} else
3020 				start = end = fClickOffset;
3021 
3022 			break;
3023 		}
3024 
3025 		case B_PAGE_DOWN:
3026 		{
3027 			BPoint currentPos = PointAt(fClickOffset);
3028 
3029 			currentPos.y += Bounds().Height();
3030 			fClickOffset = OffsetAt(LineAt(currentPos) + 1);
3031 
3032 			if (shiftDown) {
3033 				if (fClickOffset >= fSelEnd) {
3034 					start = fSelStart;
3035 					end = fClickOffset;
3036 				} else {
3037 					start = fClickOffset;
3038 					end = fSelEnd;
3039 				}
3040 			} else
3041 				start = end = fClickOffset;
3042 
3043 			break;
3044 		}
3045 	}
3046 
3047 	ScrollToOffset(fClickOffset);
3048 	Select(start, end);
3049 }
3050 
3051 
3052 /*! \brief Called when an alphanumeric key is pressed.
3053 	\param bytes The string or character associated with the key.
3054 	\param numBytes The amount of bytes containes in "bytes".
3055 */
3056 void
3057 BTextView::HandleAlphaKey(const char *bytes, int32 numBytes)
3058 {
3059 	// TODO: block input if not editable (Andrew)
3060 	if (fUndo) {
3061 		_BTypingUndoBuffer_ *undoBuffer = dynamic_cast<_BTypingUndoBuffer_ *>(fUndo);
3062 		if (!undoBuffer) {
3063 			delete fUndo;
3064 			fUndo = undoBuffer = new _BTypingUndoBuffer_(this);
3065 		}
3066 		undoBuffer->InputCharacter(numBytes);
3067 	}
3068 
3069 	bool refresh = fSelStart != fText->Length();
3070 
3071 	if (fSelStart != fSelEnd) {
3072 		Highlight(fSelStart, fSelEnd);
3073 		DeleteText(fSelStart, fSelEnd);
3074 		refresh = true;
3075 	}
3076 
3077 	if (fAutoindent && numBytes == 1 && *bytes == B_ENTER) {
3078 		int32 start, offset;
3079 		start = offset = OffsetAt(LineAt(fSelStart));
3080 
3081 		while (ByteAt(offset) != '\0' &&
3082 				(ByteAt(offset) == B_TAB || ByteAt(offset) == B_SPACE))
3083 			offset++;
3084 
3085 		if (start != offset)
3086 			InsertText(Text() + start, offset - start, fSelStart, NULL);
3087 
3088 		InsertText(bytes, numBytes, fSelStart, NULL);
3089 		numBytes += offset - start;
3090 
3091 	} else
3092 		InsertText(bytes, numBytes, fSelStart, NULL);
3093 
3094 	int32 saveStart = fSelStart;
3095 	fClickOffset = fSelEnd = fSelStart = fSelStart + numBytes;
3096 
3097 	if (Window())
3098 		Refresh(saveStart, fSelEnd, refresh, true);
3099 }
3100 
3101 
3102 /*! \brief Redraw the text comprised between the two given offsets,
3103 	recalculating linebreaks if needed.
3104 	\param fromOffset The offset from where to refresh.
3105 	\param toOffset The offset where to refresh to.
3106 	\param erase If true, the function will also erase the textview content
3107 	in the parts where text isn't present.
3108 	\param scroll If true, function will scroll the view to the end offset.
3109 */
3110 void
3111 BTextView::Refresh(int32 fromOffset, int32 toOffset, bool erase, bool scroll)
3112 {
3113 	// TODO: Cleanup
3114 	float saveHeight = fTextRect.Height();
3115 	int32 fromLine = LineAt(fromOffset);
3116 	int32 toLine = LineAt(toOffset);
3117 	int32 saveFromLine = fromLine;
3118 	int32 saveToLine = toLine;
3119 	float saveLineHeight = LineHeight(fromLine);
3120 
3121 	RecalculateLineBreaks(&fromLine, &toLine);
3122 
3123 	// TODO: Maybe there is still something we can do without a window...
3124 	if (!Window())
3125 		return;
3126 
3127 	BRect bounds = Bounds();
3128 	float newHeight = fTextRect.Height();
3129 
3130 	// if the line breaks have changed, force an erase
3131 	if (fromLine != saveFromLine || toLine != saveToLine
3132 			|| newHeight != saveHeight )
3133 		erase = true;
3134 
3135 	if (newHeight != saveHeight) {
3136 		// the text area has changed
3137 		if (newHeight < saveHeight)
3138 			toLine = LineAt(BPoint(0.0f, saveHeight + fTextRect.top));
3139 		else
3140 			toLine = LineAt(BPoint(0.0f, newHeight + fTextRect.top));
3141 	}
3142 
3143 	// draw only those lines that are visible
3144 	int32 fromVisible = LineAt(BPoint(0.0f, bounds.top));
3145 	int32 toVisible = LineAt(BPoint(0.0f, bounds.bottom));
3146 	fromLine = max_c(fromVisible, fromLine);
3147 	toLine = min_c(toLine, toVisible);
3148 
3149 	int32 drawOffset = fromOffset;
3150 	if (LineHeight(fromLine) != saveLineHeight ||
3151 		 newHeight < saveHeight || fromLine < saveFromLine || fAlignment != B_ALIGN_LEFT)
3152 		drawOffset = (*fLines)[fromLine]->offset;
3153 
3154 	if (fResizable)
3155 		AutoResize(false);
3156 
3157 	DrawLines(fromLine, toLine, drawOffset, erase);
3158 
3159 	// erase the area below the text
3160 	BRect eraseRect = bounds;
3161 	eraseRect.top = fTextRect.top + (*fLines)[fLines->NumLines()]->origin;
3162 	eraseRect.bottom = fTextRect.top + saveHeight;
3163 	if (eraseRect.bottom > eraseRect.top && eraseRect.Intersects(bounds)) {
3164 		SetLowColor(ViewColor());
3165 		FillRect(eraseRect, B_SOLID_LOW);
3166 	}
3167 
3168 	// update the scroll bars if the text area has changed
3169 	if (newHeight != saveHeight)
3170 		UpdateScrollbars();
3171 
3172 	if (scroll)
3173 		ScrollToSelection();
3174 
3175 	Flush();
3176 }
3177 
3178 
3179 void
3180 BTextView::RecalculateLineBreaks(int32 *startLine, int32 *endLine)
3181 {
3182 	// are we insane?
3183 	*startLine = (*startLine < 0) ? 0 : *startLine;
3184 	*endLine = (*endLine > fLines->NumLines() - 1) ? fLines->NumLines() - 1 : *endLine;
3185 
3186 	int32 textLength = fText->Length();
3187 	int32 lineIndex = (*startLine > 0) ? *startLine - 1 : 0;
3188 	int32 recalThreshold = (*fLines)[*endLine + 1]->offset;
3189 	float width = fTextRect.Width();
3190 	STELine* curLine = (*fLines)[lineIndex];
3191 	STELine* nextLine = curLine + 1;
3192 
3193 	do {
3194 		float ascent, descent;
3195 		int32 fromOffset = curLine->offset;
3196 		int32 toOffset = FindLineBreak(fromOffset, &ascent, &descent, &width);
3197 
3198 		// we want to advance at least by one character
3199 		int32 nextOffset = NextInitialByte(fromOffset);
3200 		if (toOffset < nextOffset && fromOffset < textLength)
3201 			toOffset = nextOffset;
3202 
3203 		// set the ascent of this line
3204 		curLine->ascent = ascent;
3205 
3206 		lineIndex++;
3207 		STELine saveLine = *nextLine;
3208 		if ( lineIndex > fLines->NumLines() ||
3209 			 toOffset < nextLine->offset ) {
3210 			// the new line comes before the old line start, add a line
3211 			STELine newLine;
3212 			newLine.offset = toOffset;
3213 			newLine.origin = ceilf(curLine->origin + ascent + descent) + 1;
3214 			newLine.ascent = 0;
3215 			fLines->InsertLine(&newLine, lineIndex);
3216 		} else {
3217 			// update the exising line
3218 			nextLine->offset = toOffset;
3219 			nextLine->origin = ceilf(curLine->origin + ascent + descent) + 1;
3220 
3221 			// remove any lines that start before the current line
3222 			while ( lineIndex < fLines->NumLines() &&
3223 					toOffset >= ((*fLines)[lineIndex] + 1)->offset )
3224 				fLines->RemoveLines(lineIndex + 1);
3225 
3226 			nextLine = (*fLines)[lineIndex];
3227 			if (nextLine->offset == saveLine.offset) {
3228 				if (nextLine->offset >= recalThreshold) {
3229 					if (nextLine->origin != saveLine.origin)
3230 						fLines->BumpOrigin(nextLine->origin - saveLine.origin,
3231 										  lineIndex + 1);
3232 					break;
3233 				}
3234 			} else {
3235 				if (lineIndex > 0 && lineIndex == *startLine)
3236 					*startLine = lineIndex - 1;
3237 			}
3238 		}
3239 
3240 		curLine = (*fLines)[lineIndex];
3241 		nextLine = curLine + 1;
3242 	} while (curLine->offset < textLength);
3243 
3244 	// update the text rect
3245 	float newHeight = TextHeight(0, fLines->NumLines() - 1);
3246 	fTextRect.bottom = fTextRect.top + newHeight;
3247 
3248 	*endLine = lineIndex - 1;
3249 	*startLine = min_c(*startLine, *endLine);
3250 }
3251 
3252 
3253 int32
3254 BTextView::FindLineBreak(int32 fromOffset, float *outAscent, float *outDescent, float *ioWidth)
3255 {
3256 	*outAscent = 0.0;
3257 	*outDescent = 0.0;
3258 
3259 	const int32 limit = fText->Length();
3260 
3261 	// is fromOffset at the end?
3262 	if (fromOffset >= limit) {
3263 		// try to return valid height info anyway
3264 		if (fStyles->NumRuns() > 0)
3265 			fStyles->Iterate(fromOffset, 1, fInline, NULL, NULL, outAscent, outDescent);
3266 		else {
3267 			if (fStyles->IsValidNullStyle()) {
3268 				const BFont *font = NULL;
3269 				fStyles->GetNullStyle(&font, NULL);
3270 
3271 				font_height fh;
3272 				font->GetHeight(&fh);
3273 				*outAscent = fh.ascent;
3274 				*outDescent = fh.descent + fh.leading;
3275 			}
3276 		}
3277 
3278 		return limit;
3279 	}
3280 
3281 	int32 offset = fromOffset;
3282 
3283 	// Text wrapping is turned off.
3284 	// Just find the offset of the first \n character
3285 	if (!fWrap) {
3286 		offset = limit - fromOffset;
3287 		fText->FindChar(B_ENTER, fromOffset, &offset);
3288 		offset += fromOffset;
3289 		offset = (offset < limit) ? offset + 1 : limit;
3290 
3291 		*ioWidth = StyledWidth(fromOffset, offset - fromOffset, outAscent, outDescent);
3292 
3293 		return offset;
3294 	}
3295 
3296 	bool done = false;
3297 	float ascent = 0.0;
3298 	float descent = 0.0;
3299 	int32 delta = 0;
3300 	float deltaWidth = 0.0;
3301 	float tabWidth = 0.0;
3302 	float strWidth = 0.0;
3303 
3304 	// wrap the text
3305 	do {
3306 		bool foundTab = false;
3307 
3308 		// find the next line break candidate
3309 		for ( ; (offset + delta) < limit ; delta++) {
3310 			if (CanEndLine(offset + delta))
3311 				break;
3312 		}
3313 		for ( ; (offset + delta) < limit; delta++) {
3314 			uchar theChar = (*fText)[offset + delta];
3315 			if (!CanEndLine(offset + delta))
3316 				break;
3317 
3318 			if (theChar == B_ENTER) {
3319 				// found a newline, we're done!
3320 				done = true;
3321 				delta++;
3322 				break;
3323 			} else {
3324 				// include all trailing spaces and tabs,
3325 				// but not spaces after tabs
3326 				if (theChar != B_SPACE && theChar != B_TAB)
3327 					break;
3328 				else {
3329 					if (theChar == B_SPACE && foundTab)
3330 						break;
3331 					else {
3332 						if (theChar == B_TAB)
3333 							foundTab = true;
3334 					}
3335 				}
3336 			}
3337 		}
3338 		delta = max_c(delta, 1);
3339 
3340 		deltaWidth = StyledWidth(offset, delta, &ascent, &descent);
3341 		strWidth += deltaWidth;
3342 
3343 		if (!foundTab)
3344 			tabWidth = 0.0;
3345 		else {
3346 			int32 tabCount = 0;
3347 			for (int32 i = delta - 1; (*fText)[offset + i] == B_TAB; i--)
3348 				tabCount++;
3349 
3350 			tabWidth = ActualTabWidth(strWidth);
3351 			if (tabCount > 1)
3352 				tabWidth += ((tabCount - 1) * fTabWidth);
3353 			strWidth += tabWidth;
3354 		}
3355 
3356 		if (strWidth >= *ioWidth) {
3357 			// we've found where the line will wrap
3358 			bool foundNewline = done;
3359 			done = true;
3360 			int32 pos = delta - 1;
3361 			if (!CanEndLine(offset + pos))
3362 				break;
3363 
3364 			strWidth -= (deltaWidth + tabWidth);
3365 
3366 			for ( ; ((offset + pos) > offset); pos--) {
3367 				if (!CanEndLine(offset + pos))
3368 					break;
3369 			}
3370 
3371 			strWidth += StyledWidth(offset, pos + 1, &ascent, &descent);
3372 			if (strWidth >= *ioWidth)
3373 				break;
3374 
3375 			if (!foundNewline) {
3376 				for ( ; (offset + delta) < limit; delta++) {
3377 					if ((*fText)[offset + delta] != B_SPACE &&
3378 						(*fText)[offset + delta] != B_TAB)
3379 						break;
3380 				}
3381 				if ( (offset + delta) < limit &&
3382 					 (*fText)[offset + delta] == B_ENTER )
3383 					delta++;
3384 			}
3385 			// get the ascent and descent of the spaces/tabs
3386 			StyledWidth(offset, delta, &ascent, &descent);
3387 		}
3388 
3389 		*outAscent = max_c(ascent, *outAscent);
3390 		*outDescent = max_c(descent, *outDescent);
3391 
3392 		offset += delta;
3393 		delta = 0;
3394 	} while (offset < limit && !done);
3395 
3396 	if (offset - fromOffset < 1) {
3397 		// there weren't any words that fit entirely in this line
3398 		// force a break in the middle of a word
3399 		*outAscent = 0.0;
3400 		*outDescent = 0.0;
3401 		strWidth = 0.0;
3402 
3403 		int32 current = fromOffset;
3404 		for (offset = fromOffset; offset <= limit; current = offset, offset = NextInitialByte(offset)) {
3405 			strWidth += StyledWidth(current, offset - current, &ascent, &descent);
3406 
3407 			if (strWidth >= *ioWidth) {
3408 				offset = PreviousInitialByte(offset);
3409 				break;
3410 			}
3411 
3412 			*outAscent = max_c(ascent, *outAscent);
3413 			*outDescent = max_c(descent, *outDescent);
3414 		}
3415 	}
3416 
3417 	return min_c(offset, limit);
3418 }
3419 
3420 
3421 /*! \brief Calculate the width of the text within the given limits.
3422 	\param fromOffset The offset where to start.
3423 	\param length The length of the text to examine.
3424 	\param outAscent A pointer to a float which will contain the maximum ascent.
3425 	\param outDescent A pointer to a float which will contain the maximum descent.
3426 	\return The width for the text within the given limits.
3427 */
3428 float
3429 BTextView::StyledWidth(int32 fromOffset, int32 length, float *outAscent,
3430 							 float *outDescent) const
3431 {
3432 	float result = 0.0;
3433 	float ascent = 0.0;
3434 	float descent = 0.0;
3435 	float maxAscent = 0.0;
3436 	float maxDescent = 0.0;
3437 
3438 	// iterate through the style runs
3439 	const BFont *font = NULL;
3440 	int32 numChars;
3441 	while ((numChars = fStyles->Iterate(fromOffset, length, fInline, &font, NULL, &ascent, &descent)) != 0) {
3442 		maxAscent = max_c(ascent, maxAscent);
3443 		maxDescent = max_c(descent, maxDescent);
3444 
3445 #if USE_WIDTHBUFFER
3446 		// Use _BWidthBuffer_ if possible
3447 		if (sWidths != NULL) {
3448 			LockWidthBuffer();
3449 			result += sWidths->StringWidth(*fText, fromOffset, numChars, font);
3450 			UnlockWidthBuffer();
3451 		} else
3452 #endif
3453 			result += font->StringWidth(fText->Text() + fromOffset, numChars);
3454 
3455 		fromOffset += numChars;
3456 		length -= numChars;
3457 	}
3458 
3459 	if (outAscent != NULL)
3460 		*outAscent = maxAscent;
3461 	if (outDescent != NULL)
3462 		*outDescent = maxDescent;
3463 
3464 	return result;
3465 }
3466 
3467 
3468 // Unlike the StyledWidth method, this one takes as parameter
3469 // the number of chars, not the number of bytes.
3470 float
3471 BTextView::StyledWidthUTF8Safe(int32 fromOffset, int32 numChars,
3472 					float *outAscent, float *outDescent) const
3473 {
3474 	int32 toOffset = fromOffset;
3475 	while (numChars--)
3476 		toOffset = NextInitialByte(toOffset);
3477 
3478 	const int32 length = toOffset - fromOffset;
3479 	return StyledWidth(fromOffset, length, outAscent, outDescent);
3480 }
3481 
3482 
3483 /*! \brief Calculate the actual tab width for the given location.
3484 	\param location The location to calculate the tab width of.
3485 	\return The actual tab width for the given location
3486 */
3487 float
3488 BTextView::ActualTabWidth(float location) const
3489 {
3490 	return fTabWidth - fmod(location, fTabWidth);
3491 }
3492 
3493 
3494 void
3495 BTextView::DoInsertText(const char *inText, int32 inLength, int32 inOffset,
3496 				const text_run_array *inRuns, _BTextChangeResult_ *outResult)
3497 {
3498 	CancelInputMethod();
3499 
3500 	// Don't do any check, the public methods will have adjusted
3501 	// eventual bogus values...
3502 
3503 	const int32 textLength = TextLength();
3504 	if (inOffset > textLength)
3505 		inOffset = textLength;
3506 
3507 	// copy data into buffer
3508 	InsertText(inText, inLength, inOffset, inRuns);
3509 
3510 	// offset the caret/selection
3511 	int32 saveStart = fSelStart;
3512 	fSelStart += inLength;
3513 	fSelEnd += inLength;
3514 
3515 	// recalc line breaks and draw the text
3516 	Refresh(saveStart, fSelEnd, true, false);
3517 }
3518 
3519 
3520 void
3521 BTextView::DoDeleteText(int32 fromOffset, int32 toOffset, _BTextChangeResult_ *outResult)
3522 {
3523 	CALLED();
3524 }
3525 
3526 
3527 void
3528 BTextView::_DrawLine(BView *view, const int32 &lineNum, const int32 &startOffset,
3529 			const bool &erase, BRect &eraseRect, BRegion &inputRegion)
3530 {
3531 	STELine *line = (*fLines)[lineNum];
3532 	float startLeft = fTextRect.left;
3533 	if (startOffset != -1) {
3534 		if (ByteAt(startOffset) == B_ENTER) {
3535 			// StartOffset is a newline
3536 			startLeft = PointAt(line->offset).x;
3537 		} else
3538 			startLeft = PointAt(startOffset).x;
3539 	}
3540 
3541 	int32 length = (line + 1)->offset;
3542 	if (startOffset != -1)
3543 		length -= startOffset;
3544 	else
3545 		length -= line->offset;
3546 
3547 	// DrawString() chokes if you draw a newline
3548 	if (ByteAt((line + 1)->offset - 1) == B_ENTER)
3549 		length--;
3550 	if (fAlignment != B_ALIGN_LEFT) {
3551 		// B_ALIGN_RIGHT
3552 		startLeft = (fTextRect.right - LineWidth(lineNum));
3553 		if (fAlignment == B_ALIGN_CENTER)
3554 			startLeft /= 2;
3555 		startLeft += fTextRect.left;
3556 	}
3557 	view->MovePenTo(startLeft, line->origin + line->ascent + fTextRect.top + 1);
3558 	if (erase) {
3559 		eraseRect.top = line->origin + fTextRect.top;
3560 		eraseRect.bottom = (line + 1)->origin + fTextRect.top;
3561 
3562 		view->FillRect(eraseRect, B_SOLID_LOW);
3563 	}
3564 
3565 	// do we have any text to draw?
3566 	if (length > 0) {
3567 		bool foundTab = false;
3568 		int32 tabChars = 0;
3569 		int32 numTabs = 0;
3570 		int32 offset = startOffset != -1 ? startOffset : line->offset;
3571 		const BFont *font = NULL;
3572 		const rgb_color *color = NULL;
3573 		int32 numBytes;
3574 		// iterate through each style on this line
3575 		while ((numBytes = fStyles->Iterate(offset, length, fInline, &font, &color)) != 0) {
3576 			view->SetFont(font);
3577 			view->SetHighColor(*color);
3578 
3579 			tabChars = numBytes;
3580 			do {
3581 				foundTab = fText->FindChar(B_TAB, offset, &tabChars);
3582 				if (foundTab) {
3583 					do {
3584 						numTabs++;
3585 						if (ByteAt(offset + tabChars + numTabs) != B_TAB)
3586 							break;
3587 					} while ((tabChars + numTabs) < numBytes);
3588 				}
3589 
3590 				if (inputRegion.CountRects() > 0) {
3591 					BRegion textRegion;
3592 					GetTextRegion(offset, offset + length, &textRegion);
3593 
3594 					textRegion.IntersectWith(&inputRegion);
3595 					view->PushState();
3596 
3597 					// Highlight in blue the inputted text
3598 					view->SetHighColor(kBlueInputColor);
3599 					view->FillRect(textRegion.Frame());
3600 
3601 					// Highlight in red the selected part
3602 					if (fInline->SelectionLength() > 0) {
3603 						BRegion selectedRegion;
3604 						GetTextRegion(fInline->Offset() + fInline->SelectionOffset(),
3605 									fInline->Offset() + fInline->SelectionOffset() + fInline->SelectionLength(),
3606 									&selectedRegion);
3607 
3608 						textRegion.IntersectWith(&selectedRegion);
3609 
3610 						view->SetHighColor(kRedInputColor);
3611 						view->FillRect(textRegion.Frame());
3612 					}
3613 
3614 					view->PopState();
3615 				}
3616 
3617 				int32 returnedBytes = tabChars;
3618 				view->DrawString(fText->GetString(offset, &returnedBytes), returnedBytes);
3619 
3620 				if (foundTab) {
3621 					float penPos = PenLocation().x - fTextRect.left;
3622 					float tabWidth = ActualTabWidth(penPos);
3623 					if (numTabs > 1)
3624 						tabWidth += ((numTabs - 1) * fTabWidth);
3625 
3626 					view->MovePenBy(tabWidth, 0.0);
3627 					tabChars += numTabs;
3628 				}
3629 
3630 				offset += tabChars;
3631 				length -= tabChars;
3632 				numBytes -= tabChars;
3633 				tabChars = numBytes;
3634 				numTabs = 0;
3635 			} while (foundTab && tabChars > 0);
3636 		}
3637 	}
3638 }
3639 
3640 
3641 void
3642 BTextView::DrawLines(int32 startLine, int32 endLine, int32 startOffset, bool erase)
3643 {
3644 	if (!Window())
3645 		return;
3646 
3647 	// clip the text
3648 	BRect clipRect = Bounds() & fTextRect;
3649 	clipRect.InsetBy(-1, -1);
3650 
3651 	BRegion newClip;
3652 	newClip.Set(clipRect);
3653 	ConstrainClippingRegion(&newClip);
3654 
3655 	// set the low color to the view color so that
3656 	// drawing to a non-white background will work
3657 	SetLowColor(ViewColor());
3658 
3659 	BView *view = NULL;
3660 	if (fOffscreen == NULL)
3661 		view = this;
3662 	else {
3663 		fOffscreen->Lock();
3664 		view = fOffscreen->ChildAt(0);
3665 		view->SetLowColor(ViewColor());
3666 		view->FillRect(view->Bounds(), B_SOLID_LOW);
3667 	}
3668 
3669 	long maxLine = fLines->NumLines() - 1;
3670 	if (startLine < 0)
3671 		startLine = 0;
3672 	if (endLine > maxLine)
3673 		endLine = maxLine;
3674 
3675 	// TODO: See if we can avoid this
3676 	if (fAlignment != B_ALIGN_LEFT)
3677 		erase = true;
3678 
3679 	// Actually hide the caret
3680 	if (fCaretVisible)
3681 		DrawCaret(fSelStart);
3682 
3683 	BRect eraseRect = clipRect;
3684 	int32 startEraseLine = startLine;
3685 	STELine* line = (*fLines)[startLine];
3686 	if (erase && startOffset != -1 && fAlignment == B_ALIGN_LEFT) {
3687 		// erase only to the right of startOffset
3688 		startEraseLine++;
3689 		long startErase = startOffset;
3690 
3691 		BPoint erasePoint = PointAt(startErase);
3692 		eraseRect.left = erasePoint.x;
3693 		eraseRect.top = erasePoint.y;
3694 		eraseRect.bottom = (line + 1)->origin + fTextRect.top;
3695 
3696 		view->FillRect(eraseRect, B_SOLID_LOW);
3697 
3698 		eraseRect = clipRect;
3699 	}
3700 
3701 	BRegion inputRegion;
3702 	if (fInline != NULL && fInline->IsActive())
3703 		GetTextRegion(fInline->Offset(), fInline->Offset() + fInline->Length(), &inputRegion);
3704 
3705 	//BPoint leftTop(startLeft, line->origin);
3706 	for (int32 lineNum = startLine; lineNum <= endLine; lineNum++) {
3707 		const bool eraseThisLine = erase && lineNum >= startEraseLine;
3708 		_DrawLine(view, lineNum, startOffset, eraseThisLine, eraseRect, inputRegion);
3709 		startOffset = -1;
3710 			// Set this to -1 so the next iteration will use the line offset
3711 	}
3712 
3713 	// draw the caret/hilite the selection
3714 	if (fActive) {
3715 		if (fSelStart != fSelEnd && fSelectable)
3716 			Highlight(fSelStart, fSelEnd);
3717 		else {
3718 			if (fCaretVisible)
3719 				DrawCaret(fSelStart);
3720 		}
3721 	}
3722 
3723 	if (fOffscreen != NULL) {
3724 		view->Sync();
3725 		/*BPoint penLocation = view->PenLocation();
3726 		BRect drawRect(leftTop.x, leftTop.y, penLocation.x, penLocation.y);
3727 		DrawBitmap(fOffscreen, drawRect, drawRect);*/
3728 		fOffscreen->Unlock();
3729 	}
3730 
3731 	ConstrainClippingRegion(NULL);
3732 }
3733 
3734 
3735 void
3736 BTextView::DrawCaret(int32 offset)
3737 {
3738 	float lineHeight;
3739 	BPoint caretPoint = PointAt(offset, &lineHeight);
3740 	caretPoint.x = min_c(caretPoint.x, fTextRect.right);
3741 
3742 	BRect caretRect;
3743 	caretRect.left = caretRect.right = caretPoint.x;
3744 	caretRect.top = caretPoint.y;
3745 	caretRect.bottom = caretPoint.y + lineHeight - 1;
3746 
3747 	InvertRect(caretRect);
3748 }
3749 
3750 
3751 /*! \brief Inverts the blinking caret status.
3752 	Hides the caret if it is being shown, and if it's hidden, shows it.
3753 */
3754 void
3755 BTextView::InvertCaret()
3756 {
3757 	DrawCaret(fSelStart);
3758 	fCaretVisible = !fCaretVisible;
3759 	fCaretTime = system_time();
3760 }
3761 
3762 
3763 /*! \brief Place the dragging caret at the given offset.
3764 	\param offset The offset (zero based within the object's text) where to place
3765 	the dragging caret. If it's -1, hide the caret.
3766 */
3767 void
3768 BTextView::DragCaret(int32 offset)
3769 {
3770 	// does the caret need to move?
3771 	if (offset == fDragOffset)
3772 		return;
3773 
3774 	// hide the previous drag caret
3775 	if (fDragOffset != -1)
3776 		DrawCaret(fDragOffset);
3777 
3778 	// do we have a new location?
3779 	if (offset != -1) {
3780 		if (fActive) {
3781 			// ignore if offset is within active selection
3782 			if (offset >= fSelStart && offset <= fSelEnd) {
3783 				fDragOffset = -1;
3784 				return;
3785 			}
3786 		}
3787 
3788 		DrawCaret(offset);
3789 	}
3790 
3791 	fDragOffset = offset;
3792 }
3793 
3794 
3795 void
3796 BTextView::StopMouseTracking()
3797 {
3798 	delete fTrackingMouse;
3799 	fTrackingMouse = NULL;
3800 }
3801 
3802 
3803 bool
3804 BTextView::PerformMouseUp(BPoint where)
3805 {
3806 	if (fTrackingMouse == NULL)
3807 		return false;
3808 
3809 	if (fTrackingMouse->selectionRect.IsValid() && fTrackingMouse->selectionRect.Contains(where))
3810 		Select(fTrackingMouse->clickOffset, fTrackingMouse->clickOffset);
3811 
3812 	StopMouseTracking();
3813 
3814 	return true;
3815 }
3816 
3817 
3818 bool
3819 BTextView::PerformMouseMoved(BPoint where, uint32 code)
3820 {
3821 	fWhere = where;
3822 
3823 	if (fTrackingMouse == NULL)
3824 		return false;
3825 
3826 	if (fTrackingMouse->selectionRect.IsValid() && fTrackingMouse->selectionRect.Contains(where)) {
3827 		StopMouseTracking();
3828 		InitiateDrag();
3829 		return true;
3830 	}
3831 
3832 	int32 oldOffset = fTrackingMouse->anchor;
3833 	int32 currentOffset = OffsetAt(where);
3834 
3835 	switch (fClickCount) {
3836 		case 0:
3837 			// triple click, select line by line
3838 			fTrackingMouse->selStart = (*fLines)[LineAt(fTrackingMouse->selStart)]->offset;
3839 			fTrackingMouse->selEnd = (*fLines)[LineAt(fTrackingMouse->selEnd) + 1]->offset;
3840 			break;
3841 
3842 		case 2:
3843 			// double click, select word by word
3844 			FindWord(currentOffset, &fTrackingMouse->selStart, &fTrackingMouse->selEnd);
3845 			break;
3846 
3847 		default:
3848 			// new click, select char by char
3849 			if (oldOffset < currentOffset) {
3850 				fTrackingMouse->selStart = oldOffset;
3851 				fTrackingMouse->selEnd = currentOffset;
3852 			} else {
3853 				fTrackingMouse->selStart = currentOffset;
3854 				fTrackingMouse->selEnd = oldOffset;
3855 			}
3856 			break;
3857 	}
3858 
3859 	Select(fTrackingMouse->selStart, fTrackingMouse->selEnd);
3860 
3861 	TrackMouse(where, NULL);
3862 
3863 	return true;
3864 }
3865 
3866 
3867 /*! \brief Tracks the mouse position, doing special actions like changing the view cursor.
3868 	\param where The point where the mouse has moved.
3869 	\param message The dragging message, if there is any.
3870 	\param force Passed as second parameter of SetViewCursor()
3871 */
3872 void
3873 BTextView::TrackMouse(BPoint where, const BMessage *message, bool force)
3874 {
3875 	BRegion textRegion;
3876 	GetTextRegion(fSelStart, fSelEnd, &textRegion);
3877 
3878 	if (message && AcceptsDrop(message))
3879 		TrackDrag(where);
3880 	else if ((fSelectable || fEditable) && !textRegion.Contains(where))
3881 		SetViewCursor(B_CURSOR_I_BEAM, force);
3882 	else
3883 		SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, force);
3884 }
3885 
3886 
3887 /*! \brief Tracks the mouse position when the user is dragging some data.
3888 	\param where The point where the mouse has moved.
3889 */
3890 void
3891 BTextView::TrackDrag(BPoint where)
3892 {
3893 	CALLED();
3894 	if (Bounds().Contains(where))
3895 		DragCaret(OffsetAt(where));
3896 }
3897 
3898 
3899 /*! \brief Function called to initiate a drag operation.
3900 */
3901 void
3902 BTextView::InitiateDrag()
3903 {
3904 	BMessage *dragMessage = new BMessage(B_MIME_DATA);
3905 	BBitmap *dragBitmap = NULL;
3906 	BPoint bitmapPoint;
3907 	BHandler *dragHandler = NULL;
3908 
3909 	GetDragParameters(dragMessage, &dragBitmap, &bitmapPoint, &dragHandler);
3910 	SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
3911 
3912 	if (dragBitmap != NULL)
3913 		DragMessage(dragMessage, dragBitmap, bitmapPoint, dragHandler);
3914 	else {
3915 		BRegion region;
3916 		GetTextRegion(fSelStart, fSelEnd, &region);
3917 		BRect bounds = Bounds();
3918 		BRect dragRect = region.Frame();
3919 		if (!bounds.Contains(dragRect))
3920 			dragRect = bounds & dragRect;
3921 
3922 		DragMessage(dragMessage, dragRect, dragHandler);
3923 	}
3924 
3925 	BMessenger messenger(this);
3926 	BMessage message(_DISPOSE_DRAG_);
3927 	fDragRunner = new (nothrow) BMessageRunner(messenger, &message, 100000);
3928 }
3929 
3930 
3931 /*! \brief Called when some data is dropped on the view.
3932 	\param inMessage The message which has been dropped.
3933 	\param where The location where the message has been dropped.
3934 	\param offset ?
3935 	\return \c true if the message was handled, \c false if not.
3936 */
3937 bool
3938 BTextView::MessageDropped(BMessage *inMessage, BPoint where, BPoint offset)
3939 {
3940 	ASSERT(inMessage);
3941 
3942 	void *from = NULL;
3943 	bool internalDrop = false;
3944 	if (inMessage->FindPointer("be:originator", &from) == B_OK
3945 			&& from == this && fSelEnd != fSelStart)
3946 		internalDrop = true;
3947 
3948 	DragCaret(-1);
3949 
3950 	delete fDragRunner;
3951 	fDragRunner = NULL;
3952 
3953 	TrackMouse(where, NULL);
3954 
3955 	// are we sure we like this message?
3956 	if (!AcceptsDrop(inMessage))
3957 		return false;
3958 
3959 	int32 dropOffset = OffsetAt(where);
3960 	if (dropOffset > TextLength())
3961 		dropOffset = TextLength();
3962 
3963 	// if this view initiated the drag, move instead of copy
3964 	if (internalDrop) {
3965 		// dropping onto itself?
3966 		if (dropOffset >= fSelStart && dropOffset <= fSelEnd)
3967 			return true;
3968 	}
3969 
3970 	ssize_t dataLen = 0;
3971 	const char *text = NULL;
3972 	if (inMessage->FindData("text/plain", B_MIME_TYPE, (const void **)&text, &dataLen) == B_OK) {
3973 
3974 		text_run_array *runArray = NULL;
3975 		ssize_t runLen = 0;
3976 		if (fStylable)
3977 			inMessage->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE,
3978 					(const void **)&runArray, &runLen);
3979 
3980 		if (fUndo) {
3981 			delete fUndo;
3982 			fUndo = new _BDropUndoBuffer_(this, text, dataLen, runArray, runLen, dropOffset, internalDrop);
3983 		}
3984 
3985 		if (internalDrop) {
3986 			if (dropOffset > fSelEnd)
3987 				dropOffset -= dataLen;
3988 			Delete();
3989 		}
3990 
3991 		Insert(dropOffset, text, dataLen, runArray);
3992 	}
3993 
3994 	return true;
3995 }
3996 
3997 
3998 void
3999 BTextView::PerformAutoScrolling()
4000 {
4001 	// Scroll the view a bit if mouse is outside the view bounds
4002 	BRect bounds = Bounds();
4003 	BPoint scrollBy;
4004 
4005 	BPoint constraint = fWhere;
4006 	constraint.ConstrainTo(bounds);
4007 	// Scroll char by char horizontally
4008 	// TODO: Check how BeOS R5 behaves
4009 	float value = StyledWidthUTF8Safe(OffsetAt(constraint), 1);
4010 	if (fWhere.x > bounds.right) {
4011 		if (bounds.right + value <= fTextRect.Width())
4012 			scrollBy.x = value;
4013 	} else if (fWhere.x < bounds.left) {
4014 		if (bounds.left - value >= 0)
4015 			scrollBy.x = -value;
4016 	}
4017 
4018 	float lineHeight = 0;
4019 	float vertDiff = 0;
4020 	if (fWhere.y > bounds.bottom) {
4021 		lineHeight = LineHeight(LineAt(bounds.LeftBottom()));
4022 		vertDiff = fWhere.y - bounds.bottom;
4023 	} else if (fWhere.y < bounds.top) {
4024 		lineHeight = LineHeight(LineAt(bounds.LeftTop()));
4025 		vertDiff = fWhere.y - bounds.top; // negative value
4026 	}
4027 
4028 	// Always scroll vertically line by line or by multiples of that
4029 	// based on the distance of the cursor from the border of the view
4030 	// TODO: Refine this, I can't even remember how beos works here
4031 	scrollBy.y = lineHeight > 0 ? lineHeight * (int32)(floorf(vertDiff) / lineHeight) : 0;
4032 
4033 	if (scrollBy != B_ORIGIN)
4034 		ScrollBy(scrollBy.x, scrollBy.y);
4035 }
4036 
4037 
4038 /*! \brief Updates the scrollbars associated with the object (if any).
4039 */
4040 void
4041 BTextView::UpdateScrollbars()
4042 {
4043 	BRect bounds(Bounds());
4044 	BScrollBar *horizontalScrollBar = ScrollBar(B_HORIZONTAL);
4045  	BScrollBar *verticalScrollBar = ScrollBar(B_VERTICAL);
4046 
4047 	// do we have a horizontal scroll bar?
4048 	if (horizontalScrollBar != NULL) {
4049 		long viewWidth = bounds.IntegerWidth();
4050 		long dataWidth = fTextRect.IntegerWidth();
4051 		dataWidth += (long)ceilf(fTextRect.left) + 1;
4052 
4053 		long maxRange = dataWidth - viewWidth;
4054 		maxRange = max_c(maxRange, 0);
4055 
4056 		horizontalScrollBar->SetRange(0, (float)maxRange);
4057 		horizontalScrollBar->SetProportion((float)viewWidth / (float)dataWidth);
4058 		horizontalScrollBar->SetSteps(10, dataWidth / 10);
4059 	}
4060 
4061 	// how about a vertical scroll bar?
4062 	if (verticalScrollBar != NULL) {
4063 		long viewHeight = bounds.IntegerHeight();
4064 		long dataHeight = fTextRect.IntegerHeight();
4065 		dataHeight += (long)ceilf(fTextRect.top) + 1;
4066 
4067 		long maxRange = dataHeight - viewHeight;
4068 		maxRange = max_c(maxRange, 0);
4069 
4070 		verticalScrollBar->SetRange(0, maxRange);
4071 		verticalScrollBar->SetProportion((float)viewHeight / (float)dataHeight);
4072 		verticalScrollBar->SetSteps(12, viewHeight);
4073 	}
4074 }
4075 
4076 
4077 /*!	\brief Autoresizes the view to fit the contained text.
4078 */
4079 void
4080 BTextView::AutoResize(bool redraw)
4081 {
4082 	// TODO: Review this
4083 	if (fResizable) {
4084 		float oldWidth = Bounds().Width() + 1;
4085 		float newWidth = 0;
4086 		for (int32 i = 0; i < CountLines(); i++)
4087 			newWidth += LineWidth(i);
4088 
4089 		if (newWidth < 3)
4090 			newWidth = 3;
4091 		BRect newRect(0, 0, ceilf(newWidth) + 1, ceilf(TextHeight(0, 0)) + 1);
4092 
4093 		if (fContainerView != NULL) {
4094 			fContainerView->ResizeTo(newRect.Width(), newRect.Height());
4095 			if (fAlignment == B_ALIGN_CENTER)
4096 				fContainerView->MoveBy((oldWidth - newRect.Width()) / 2, 0);
4097 			else if (fAlignment == B_ALIGN_RIGHT)
4098 				fContainerView->MoveBy(oldWidth - newRect.Width(), 0);
4099 			fContainerView->Invalidate();
4100 		}
4101 
4102 		fTextRect = newRect.InsetBySelf(1, 1);
4103 
4104 		if (redraw)
4105 			DrawLines(0, 0);
4106 
4107 		// Erase the old text (TODO: Might not work for alignments different than B_ALIGN_LEFT)
4108 		SetLowColor(ViewColor());
4109 		FillRect(BRect(fTextRect.right, fTextRect.top, Bounds().right, fTextRect.bottom), B_SOLID_LOW);
4110 	}
4111 }
4112 
4113 
4114 /*! \brief Creates a new offscreen BBitmap with an associated BView.
4115 	param padding Padding (?)
4116 
4117 	Creates an offscreen BBitmap which will be used to draw.
4118 */
4119 void
4120 BTextView::NewOffscreen(float padding)
4121 {
4122 	if (fOffscreen != NULL)
4123 		DeleteOffscreen();
4124 
4125 #if USE_DOUBLEBUFFERING
4126 	BRect bitmapRect(0, 0, fTextRect.Width() + padding, fTextRect.Height());
4127 	fOffscreen = new BBitmap(bitmapRect, fColorSpace, true, false);
4128 	if (fOffscreen != NULL && fOffscreen->Lock()) {
4129 		BView *bufferView = new BView(bitmapRect, "drawing view", 0, 0);
4130 		fOffscreen->AddChild(bufferView);
4131 		fOffscreen->Unlock();
4132 	}
4133 #endif
4134 }
4135 
4136 
4137 /*! \brief Deletes the textview's offscreen bitmap, if any.
4138 */
4139 void
4140 BTextView::DeleteOffscreen()
4141 {
4142 	if (fOffscreen != NULL && fOffscreen->Lock()) {
4143 		delete fOffscreen;
4144 		fOffscreen = NULL;
4145 	}
4146 }
4147 
4148 
4149 /*!	\brief Creates a new offscreen bitmap, highlight the selection, and set the
4150 	cursor to B_CURSOR_I_BEAM.
4151 */
4152 void
4153 BTextView::Activate()
4154 {
4155 	fActive = true;
4156 
4157 	// Create a new offscreen BBitmap
4158 	NewOffscreen();
4159 
4160 	if (fSelStart != fSelEnd) {
4161 		if (fSelectable)
4162 			Highlight(fSelStart, fSelEnd);
4163 	} else {
4164 		if (fEditable)
4165 			InvertCaret();
4166 	}
4167 
4168 	BPoint where;
4169 	ulong buttons;
4170 	GetMouse(&where, &buttons, false);
4171 	if (Bounds().Contains(where))
4172 		TrackMouse(where, NULL);
4173 }
4174 
4175 
4176 /*! \brief Unhilights the selection, set the cursor to B_CURSOR_SYSTEM_DEFAULT.
4177 */
4178 void
4179 BTextView::Deactivate()
4180 {
4181 	fActive = false;
4182 
4183 	CancelInputMethod();
4184 	DeleteOffscreen();
4185 
4186 	if (fSelStart != fSelEnd) {
4187 		if (fSelectable)
4188 			Highlight(fSelStart, fSelEnd);
4189 	} else {
4190 		if (fCaretVisible)
4191 			InvertCaret();
4192 	}
4193 
4194 	BPoint where;
4195 	ulong buttons;
4196 	GetMouse(&where, &buttons);
4197 	if (Bounds().Contains(where))
4198 		SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
4199 }
4200 
4201 
4202 /*! \brief Changes the passed font to be displayable by the object.
4203 	\param font A pointer to the font to normalize.
4204 
4205 	Set font rotation to 0, removes any font flag, set font spacing
4206 	to \c B_BITMAP_SPACING and font encoding to \c B_UNICODE_UTF8
4207 */
4208 void
4209 BTextView::NormalizeFont(BFont *font)
4210 {
4211 	if (font) {
4212 		font->SetRotation(0.0f);
4213 		font->SetFlags(0);
4214 		font->SetSpacing(B_BITMAP_SPACING);
4215 		font->SetEncoding(B_UNICODE_UTF8);
4216 	}
4217 }
4218 
4219 
4220 /*! \brief Returns a value which tells if the given character is a separator
4221 	character or not.
4222 	\param offset The offset where the wanted character can be found.
4223 	\return A value which represents the character's classification.
4224 */
4225 uint32
4226 BTextView::CharClassification(int32 offset) const
4227 {
4228 	// TODO:Should check against a list of characters containing also
4229 	// japanese word breakers.
4230 	// And what about other languages ? Isn't there a better way to check
4231 	// for separator characters ?
4232 	// Andrew suggested to have a look at UnicodeBlockObject.h
4233 	switch (fText->RealCharAt(offset)) {
4234 		case B_SPACE:
4235 		case '_':
4236 		case '.':
4237 		case '\0':
4238 		case B_TAB:
4239 		case B_ENTER:
4240 		case '&':
4241 		case '*':
4242 		case '+':
4243 		case '-':
4244 		case '/':
4245 		case '<':
4246 		case '=':
4247 		case '>':
4248 		case '\\':
4249 		case '^':
4250 		case '|':
4251 			return B_SEPARATOR_CHARACTER;
4252 		default:
4253 			return B_OTHER_CHARACTER;
4254 	}
4255 }
4256 
4257 
4258 /*! \brief Returns the offset of the next UTF8 character within the BTextView's text.
4259 	\param offset The offset where to start looking.
4260 	\return The offset of the next UTF8 character.
4261 */
4262 int32
4263 BTextView::NextInitialByte(int32 offset) const
4264 {
4265 	int32 textLength = TextLength();
4266 	if (offset >= textLength)
4267 		return textLength;
4268 
4269 	for (++offset; (ByteAt(offset) & 0xC0) == 0x80; ++offset)
4270 		;
4271 
4272 	return offset;
4273 }
4274 
4275 
4276 /*! \brief Returns the offset of the previous UTF8 character within the BTextView's text.
4277 	\param offset The offset where to start looking.
4278 	\return The offset of the previous UTF8 character.
4279 */
4280 int32
4281 BTextView::PreviousInitialByte(int32 offset) const
4282 {
4283 	if (offset <= 0)
4284 		return 0;
4285 
4286 	int32 count = 6;
4287 
4288 	for (--offset; offset > 0 && count; --offset, --count) {
4289 		if ((ByteAt(offset) & 0xC0) != 0x80)
4290 			break;
4291 	}
4292 
4293 	return count ? offset : 0;
4294 }
4295 
4296 
4297 bool
4298 BTextView::GetProperty(BMessage *specifier, int32 form, const char *property, BMessage *reply)
4299 {
4300 	CALLED();
4301 	if (strcmp(property, "selection") == 0) {
4302 		reply->what = B_REPLY;
4303 		reply->AddInt32("result", fSelStart);
4304 		reply->AddInt32("result", fSelEnd);
4305 		reply->AddInt32("error", B_OK);
4306 
4307 		return true;
4308 
4309 	} else if (strcmp(property, "Text") == 0) {
4310 		int32 index, range;
4311 
4312 		specifier->FindInt32("index", &index);
4313 		specifier->FindInt32("range", &range);
4314 
4315 		char *buffer = new char[range + 1];
4316 		GetText(index, range, buffer);
4317 
4318 		reply->what = B_REPLY;
4319 		reply->AddString("result", buffer);
4320 		delete buffer;
4321 		reply->AddInt32("error", B_OK);
4322 
4323 		return true;
4324 
4325 	} else if (strcmp(property, "text_run_array") == 0)
4326 		return false;
4327 	else
4328 		return false;
4329 }
4330 
4331 
4332 bool
4333 BTextView::SetProperty(BMessage *specifier, int32 form, const char *property, BMessage *reply)
4334 {
4335 	CALLED();
4336 	if (strcmp(property, "selection") == 0) {
4337 		int32 index, range;
4338 
4339 		specifier->FindInt32("index", &index);
4340 		specifier->FindInt32("range", &range);
4341 
4342 		Select(index, index + range);
4343 
4344 		reply->what = B_REPLY;
4345 		reply->AddInt32("error", B_OK);
4346 
4347 		return true;
4348 
4349 	} else if (strcmp(property, "Text") == 0) {
4350 		int32 index, range;
4351 
4352 		specifier->FindInt32("index", &index);
4353 		specifier->FindInt32("range", &range);
4354 
4355 		const char *buffer = NULL;
4356 		if (specifier->FindString("data", &buffer) == B_OK)
4357 			InsertText(buffer, range, index, NULL);
4358 		else
4359 			DeleteText(index, range);
4360 
4361 		reply->what = B_REPLY;
4362 		reply->AddInt32("error", B_OK);
4363 
4364 		return true;
4365 
4366 	} else if (strcmp(property, "text_run_array") == 0)
4367 		return false;
4368 
4369 	else
4370 		return false;
4371 }
4372 
4373 
4374 bool
4375 BTextView::CountProperties(BMessage *specifier, int32 form, const char *property, BMessage *reply)
4376 {
4377 	CALLED();
4378 	if (strcmp(property, "Text") == 0) {
4379 		reply->what = B_REPLY;
4380 		reply->AddInt32("result", TextLength());
4381 		reply->AddInt32("error", B_OK);
4382 		return true;
4383 	}
4384 
4385 	return false;
4386 }
4387 
4388 
4389 /*! \brief Called when the object receives a B_INPUT_METHOD_CHANGED message.
4390 	\param message A B_INPUT_METHOD_CHANGED message.
4391 */
4392 void
4393 BTextView::HandleInputMethodChanged(BMessage *message)
4394 {
4395 	// TODO: block input if not editable (Andrew)
4396 	if (!fInline)
4397 		return;
4398 
4399 	const char *string = NULL;
4400 	if (message->FindString("be:string", &string) < B_OK || string == NULL)
4401 		return;
4402 
4403 	be_app->ObscureCursor();
4404 
4405 	// If we find the "be:confirmed" boolean (and the boolean is true),
4406 	// it means it's over for now, so the current _BInlineInput_ object
4407 	// should become inactive. We will probably receive a B_INPUT_METHOD_STOPPED
4408 	// message after this one.
4409 	bool confirmed;
4410 	if (message->FindBool("be:confirmed", &confirmed) != B_OK)
4411 		confirmed = false;
4412 
4413 	// Delete the previously inserted text (if any)
4414 	if (fInline->IsActive()) {
4415 		int32 oldOffset = fInline->Offset();
4416 		DeleteText(oldOffset, oldOffset + fInline->Length());
4417 		fClickOffset = fSelStart = fSelEnd = oldOffset;
4418 
4419 		if (confirmed)
4420 			fInline->SetActive(false);
4421 	}
4422 
4423 	int32 stringLen = strlen(string);
4424 
4425 	fInline->SetOffset(fSelStart);
4426 	fInline->SetLength(stringLen);
4427 	fInline->ResetClauses();
4428 
4429 	// Get the clauses, and pass them to the _BInlineInput_ object
4430 	// TODO: Find out if what we did it's ok, currently we don't consider clauses
4431 	// at all, while the bebook says we should; though the visual effect we obtained
4432 	// seems correct. Weird.
4433 	int32 clauseCount = 0;
4434 	int32 clauseStart;
4435 	int32 clauseEnd;
4436 	while (message->FindInt32("be:clause_start", clauseCount, &clauseStart) == B_OK &&
4437 			message->FindInt32("be:clause_end", clauseCount, &clauseEnd) == B_OK) {
4438 		if (!fInline->AddClause(clauseStart, clauseEnd))
4439 			break;
4440 		clauseCount++;
4441 	}
4442 
4443 	int32 selectionStart = 0;
4444 	int32 selectionEnd = 0;
4445 	message->FindInt32("be:selection", 0, &selectionStart);
4446 	message->FindInt32("be:selection", 1, &selectionEnd);
4447 
4448 	fInline->SetSelectionOffset(selectionStart);
4449 	fInline->SetSelectionLength(selectionEnd - selectionStart);
4450 
4451 	// Insert the new text
4452 	InsertText(string, stringLen, fSelStart, NULL);
4453 	fSelStart += stringLen;
4454 	fClickOffset = fSelEnd = fSelStart;
4455 
4456 	if (!confirmed && !fInline->IsActive())
4457 		fInline->SetActive(true);
4458 
4459 	Refresh(fInline->Offset(), fSelEnd, true, false);
4460 }
4461 
4462 
4463 /*! \brief Called when the object receives a B_INPUT_METHOD_LOCATION_REQUEST message.
4464 */
4465 void
4466 BTextView::HandleInputMethodLocationRequest()
4467 {
4468 	if (!fInline)
4469 		return;
4470 
4471 	int32 offset = fInline->Offset();
4472 	const int32 limit = offset + fInline->Length();
4473 
4474 	BMessage message(B_INPUT_METHOD_EVENT);
4475 	message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
4476 
4477 	// Add the location of the UTF8 characters
4478 	while (offset < limit) {
4479 		float height;
4480 		BPoint where = PointAt(offset, &height);
4481 		ConvertToScreen(&where);
4482 
4483 		message.AddPoint("be:location_reply", where);
4484 		message.AddFloat("be:height_reply", height);
4485 
4486 		offset = NextInitialByte(offset);
4487 	}
4488 
4489 	fInline->Method()->SendMessage(&message);
4490 }
4491 
4492 
4493 /*! \brief Tells the input server method addon to stop the current transaction.
4494 */
4495 void
4496 BTextView::CancelInputMethod()
4497 {
4498 	if (!fInline)
4499 		return;
4500 
4501 	BMessage message(B_INPUT_METHOD_EVENT);
4502 	message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
4503 	fInline->Method()->SendMessage(&message);
4504 
4505 	// Delete the previously inserted text (if any)
4506 	if (fInline->IsActive()) {
4507 		int32 oldOffset = fInline->Offset();
4508 		DeleteText(oldOffset, oldOffset + fInline->Length());
4509 		fClickOffset = fSelStart = fSelEnd = oldOffset;
4510 	}
4511 
4512 	delete fInline;
4513 	fInline = NULL;
4514 
4515 	if (Window())
4516 		Refresh(0, fText->Length(), true, false);
4517 }
4518 
4519 
4520 /*! \brief Locks the static _BWidthBuffer_ object to be able to access it safely.
4521 */
4522 void
4523 BTextView::LockWidthBuffer()
4524 {
4525 	if (atomic_add(&sWidthAtom, 1) > 0) {
4526 		while (acquire_sem(sWidthSem) == B_INTERRUPTED)
4527 			;
4528 	}
4529 }
4530 
4531 
4532 /*! \brief Unlocks the static _BWidthBuffer_ object.
4533 */
4534 void
4535 BTextView::UnlockWidthBuffer()
4536 {
4537 	if (atomic_add(&sWidthAtom, -1) > 1)
4538 		release_sem(sWidthSem);
4539 }
4540