xref: /haiku/src/apps/terminal/TermView.cpp (revision 125183f9e5c136781f71c879faaeab43fdc3ea7b)
1 /*
2  * Copyright 2001-2010, Haiku, Inc.
3  * Copyright 2003-2004 Kian Duffy, myob@users.sourceforge.net
4  * Parts Copyright 1998-1999 Kazuho Okui and Takashi Murai.
5  * All rights reserved. Distributed under the terms of the MIT license.
6  *
7  * Authors:
8  *		Stefano Ceccherini <stefano.ceccherini@gmail.com>
9  *		Kian Duffy, myob@users.sourceforge.net
10  *		Y.Hayakawa, hida@sawada.riec.tohoku.ac.jp
11  *		Ingo Weinhold <ingo_weinhold@gmx.de>
12  *		Clemens Zeidler <haiku@Clemens-Zeidler.de>
13  */
14 
15 
16 #include "TermView.h"
17 
18 #include <ctype.h>
19 #include <signal.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <termios.h>
23 
24 #include <algorithm>
25 #include <new>
26 
27 #include <Alert.h>
28 #include <Application.h>
29 #include <Beep.h>
30 #include <Catalog.h>
31 #include <Clipboard.h>
32 #include <Debug.h>
33 #include <Directory.h>
34 #include <Dragger.h>
35 #include <Input.h>
36 #include <Locale.h>
37 #include <MenuItem.h>
38 #include <Message.h>
39 #include <MessageRunner.h>
40 #include <Node.h>
41 #include <Path.h>
42 #include <PopUpMenu.h>
43 #include <PropertyInfo.h>
44 #include <Region.h>
45 #include <Roster.h>
46 #include <ScrollBar.h>
47 #include <ScrollView.h>
48 #include <String.h>
49 #include <StringView.h>
50 #include <Window.h>
51 
52 #include "Encoding.h"
53 #include "InlineInput.h"
54 #include "Shell.h"
55 #include "TermConst.h"
56 #include "TerminalBuffer.h"
57 #include "TerminalCharClassifier.h"
58 #include "VTkeymap.h"
59 
60 
61 // defined in VTKeyTbl.c
62 extern int function_keycode_table[];
63 extern char *function_key_char_table[];
64 
65 const static rgb_color kTermColorTable[8] = {
66 	{ 40,  40,  40, 0},	// black
67 	{204,   0,   0, 0},	// red
68 	{ 78, 154,   6, 0},	// green
69 	{218, 168,   0, 0},	// yellow
70 	{ 51, 102, 152, 0},	// blue
71 	{115,  68, 123, 0},	// magenta
72 	{  6, 152, 154, 0},	// cyan
73 	{245, 245, 245, 0},	// white
74 };
75 
76 #define ROWS_DEFAULT 25
77 #define COLUMNS_DEFAULT 80
78 
79 // selection granularity
80 enum {
81 	SELECT_CHARS,
82 	SELECT_WORDS,
83 	SELECT_LINES
84 };
85 
86 #undef TR_CONTEXT
87 #define TR_CONTEXT "Terminal TermView"
88 
89 static property_info sPropList[] = {
90 	{ "encoding",
91 	{B_GET_PROPERTY, 0},
92 	{B_DIRECT_SPECIFIER, 0},
93 	"get terminal encoding"},
94 	{ "encoding",
95 	{B_SET_PROPERTY, 0},
96 	{B_DIRECT_SPECIFIER, 0},
97 	"set terminal encoding"},
98 	{ "tty",
99 	{B_GET_PROPERTY, 0},
100 	{B_DIRECT_SPECIFIER, 0},
101 	"get tty name."},
102 	{ 0  }
103 };
104 
105 
106 static const uint32 kUpdateSigWinch = 'Rwin';
107 static const uint32 kBlinkCursor = 'BlCr';
108 static const uint32 kAutoScroll = 'AScr';
109 
110 static const bigtime_t kSyncUpdateGranularity = 100000;	// 0.1 s
111 
112 static const int32 kCursorBlinkIntervals = 3;
113 static const int32 kCursorVisibleIntervals = 2;
114 static const bigtime_t kCursorBlinkInterval = 500000;
115 
116 static const rgb_color kBlackColor = { 0, 0, 0, 255 };
117 static const rgb_color kWhiteColor = { 255, 255, 255, 255 };
118 
119 static const char* kDefaultSpecialWordChars = ":@-./_~";
120 static const char* kEscapeCharacters = " ~`#$&*()\\|[]{};'\"<>?!";
121 
122 // secondary mouse button drop
123 const int32 kSecondaryMouseDropAction = 'SMDA';
124 
125 enum {
126 	kInsert,
127 	kChangeDirectory,
128 	kLinkFiles,
129 	kMoveFiles,
130 	kCopyFiles
131 };
132 
133 
134 template<typename Type>
135 static inline Type
136 restrict_value(const Type& value, const Type& min, const Type& max)
137 {
138 	return value < min ? min : (value > max ? max : value);
139 }
140 
141 
142 class TermView::CharClassifier : public TerminalCharClassifier {
143 public:
144 	CharClassifier(const char* specialWordChars)
145 		:
146 		fSpecialWordChars(specialWordChars)
147 	{
148 	}
149 
150 	virtual int Classify(const char* character)
151 	{
152 		// TODO: Deal correctly with non-ASCII chars.
153 		char c = *character;
154 		if (UTF8Char::ByteCount(c) > 1)
155 			return CHAR_TYPE_WORD_CHAR;
156 
157 		if (isspace(c))
158 			return CHAR_TYPE_SPACE;
159 		if (isalnum(c) || strchr(fSpecialWordChars, c) != NULL)
160 			return CHAR_TYPE_WORD_CHAR;
161 
162 		return CHAR_TYPE_WORD_DELIMITER;
163 	}
164 
165 private:
166 	const char*	fSpecialWordChars;
167 };
168 
169 
170 //	#pragma mark -
171 
172 
173 TermView::TermView(BRect frame, int32 argc, const char** argv, int32 historySize)
174 	: BView(frame, "termview", B_FOLLOW_ALL,
175 		B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
176 	fColumns(COLUMNS_DEFAULT),
177 	fRows(ROWS_DEFAULT),
178 	fEncoding(M_UTF8),
179 	fActive(false),
180 	fScrBufSize(historySize),
181 	fReportX10MouseEvent(false),
182 	fReportNormalMouseEvent(false),
183 	fReportButtonMouseEvent(false),
184 	fReportAnyMouseEvent(false)
185 {
186 	status_t status = _InitObject(argc, argv);
187 	if (status != B_OK)
188 		throw status;
189 	SetTermSize(frame);
190 }
191 
192 
193 TermView::TermView(int rows, int columns, int32 argc, const char** argv,
194 		int32 historySize)
195 	: BView(BRect(0, 0, 0, 0), "termview", B_FOLLOW_ALL,
196 		B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
197 	fColumns(columns),
198 	fRows(rows),
199 	fEncoding(M_UTF8),
200 	fActive(false),
201 	fScrBufSize(historySize),
202 	fReportX10MouseEvent(false),
203 	fReportNormalMouseEvent(false),
204 	fReportButtonMouseEvent(false),
205 	fReportAnyMouseEvent(false)
206 {
207 	status_t status = _InitObject(argc, argv);
208 	if (status != B_OK)
209 		throw status;
210 
211 	// TODO: Don't show the dragger, since replicant capabilities
212 	// don't work very well ATM.
213 	/*
214 	BRect rect(0, 0, 16, 16);
215 	rect.OffsetTo(Bounds().right - rect.Width(),
216 		Bounds().bottom - rect.Height());
217 
218 	SetFlags(Flags() | B_DRAW_ON_CHILDREN | B_FOLLOW_ALL);
219 	AddChild(new BDragger(rect, this,
220 		B_FOLLOW_RIGHT|B_FOLLOW_BOTTOM, B_WILL_DRAW));*/
221 }
222 
223 
224 TermView::TermView(BMessage* archive)
225 	:
226 	BView(archive),
227 	fColumns(COLUMNS_DEFAULT),
228 	fRows(ROWS_DEFAULT),
229 	fEncoding(M_UTF8),
230 	fActive(false),
231 	fScrBufSize(1000),
232 	fReportX10MouseEvent(false),
233 	fReportNormalMouseEvent(false),
234 	fReportButtonMouseEvent(false),
235 	fReportAnyMouseEvent(false)
236 {
237 	BRect frame = Bounds();
238 
239 	if (archive->FindInt32("encoding", (int32*)&fEncoding) < B_OK)
240 		fEncoding = M_UTF8;
241 	if (archive->FindInt32("columns", (int32*)&fColumns) < B_OK)
242 		fColumns = COLUMNS_DEFAULT;
243 	if (archive->FindInt32("rows", (int32*)&fRows) < B_OK)
244 		fRows = ROWS_DEFAULT;
245 
246 	int32 argc = 0;
247 	if (archive->HasInt32("argc"))
248 		archive->FindInt32("argc", &argc);
249 
250 	const char **argv = new const char*[argc];
251 	for (int32 i = 0; i < argc; i++) {
252 		archive->FindString("argv", i, (const char**)&argv[i]);
253 	}
254 
255 	// TODO: Retrieve colors, history size, etc. from archive
256 	status_t status = _InitObject(argc, argv);
257 	if (status != B_OK)
258 		throw status;
259 
260 	bool useRect = false;
261 	if ((archive->FindBool("use_rect", &useRect) == B_OK) && useRect)
262 		SetTermSize(frame);
263 
264 	delete[] argv;
265 }
266 
267 
268 /*!	Initializes the object for further use.
269 	The members fRows, fColumns, fEncoding, and fScrBufSize must
270 	already be initialized; they are not touched by this method.
271 */
272 status_t
273 TermView::_InitObject(int32 argc, const char** argv)
274 {
275 	SetFlags(Flags() | B_WILL_DRAW | B_FRAME_EVENTS
276 		| B_FULL_UPDATE_ON_RESIZE/* | B_INPUT_METHOD_AWARE*/);
277 
278 	fShell = NULL;
279 	fWinchRunner = NULL;
280 	fCursorBlinkRunner = NULL;
281 	fAutoScrollRunner = NULL;
282 	fResizeRunner = NULL;
283 	fResizeView = NULL;
284 	fCharClassifier = NULL;
285 	fFontWidth = 0;
286 	fFontHeight = 0;
287 	fFontAscent = 0;
288 	fFrameResized = false;
289 	fResizeViewDisableCount = 0;
290 	fLastActivityTime = 0;
291 	fCursorState = 0;
292 	fCursorHeight = 0;
293 	fCursor = TermPos(0, 0);
294 	fTextBuffer = NULL;
295 	fVisibleTextBuffer = NULL;
296 	fScrollBar = NULL;
297 	fInline = NULL;
298 	fTextForeColor = kBlackColor;
299 	fTextBackColor = kWhiteColor;
300 	fCursorForeColor = kWhiteColor;
301 	fCursorBackColor = kBlackColor;
302 	fSelectForeColor = kWhiteColor;
303 	fSelectBackColor = kBlackColor;
304 	fScrollOffset = 0;
305 	fLastSyncTime = 0;
306 	fScrolledSinceLastSync = 0;
307 	fSyncRunner = NULL;
308 	fConsiderClockedSync = false;
309 	fSelStart = TermPos(-1, -1);
310 	fSelEnd = TermPos(-1, -1);
311 	fMouseTracking = false;
312 	fCheckMouseTracking = false;
313 	fPrevPos = TermPos(-1, - 1);
314 	fReportX10MouseEvent = false;
315 	fReportNormalMouseEvent = false;
316 	fReportButtonMouseEvent = false;
317 	fReportAnyMouseEvent = false;
318 	fMouseClipboard = be_clipboard;
319 
320 	fTextBuffer = new(std::nothrow) TerminalBuffer;
321 	if (fTextBuffer == NULL)
322 		return B_NO_MEMORY;
323 
324 	fVisibleTextBuffer = new(std::nothrow) BasicTerminalBuffer;
325 	if (fVisibleTextBuffer == NULL)
326 		return B_NO_MEMORY;
327 
328 	// TODO: Make the special word chars user-settable!
329 	fCharClassifier = new(std::nothrow) CharClassifier(
330 		kDefaultSpecialWordChars);
331 	if (fCharClassifier == NULL)
332 		return B_NO_MEMORY;
333 
334 	status_t error = fTextBuffer->Init(fColumns, fRows, fScrBufSize);
335 	if (error != B_OK)
336 		return error;
337 	fTextBuffer->SetEncoding(fEncoding);
338 
339 	error = fVisibleTextBuffer->Init(fColumns, fRows + 2, 0);
340 	if (error != B_OK)
341 		return error;
342 
343 	fShell = new (std::nothrow) Shell();
344 	if (fShell == NULL)
345 		return B_NO_MEMORY;
346 
347 	SetTermFont(be_fixed_font);
348 
349 	error = fShell->Open(fRows, fColumns,
350 		EncodingAsShortString(fEncoding), argc, argv);
351 
352 	if (error < B_OK)
353 		return error;
354 
355 	error = _AttachShell(fShell);
356 	if (error < B_OK)
357 		return error;
358 
359 	SetLowColor(fTextBackColor);
360 	SetViewColor(B_TRANSPARENT_32_BIT);
361 
362 	return B_OK;
363 }
364 
365 
366 TermView::~TermView()
367 {
368 	Shell* shell = fShell;
369 		// _DetachShell sets fShell to NULL
370 
371 	_DetachShell();
372 
373 	delete fSyncRunner;
374 	delete fAutoScrollRunner;
375 	delete fCharClassifier;
376 	delete fVisibleTextBuffer;
377 	delete fTextBuffer;
378 	delete shell;
379 }
380 
381 
382 /* static */
383 BArchivable *
384 TermView::Instantiate(BMessage* data)
385 {
386 	if (validate_instantiation(data, "TermView")) {
387 		TermView *view = new (std::nothrow) TermView(data);
388 		return view;
389 	}
390 
391 	return NULL;
392 }
393 
394 
395 status_t
396 TermView::Archive(BMessage* data, bool deep) const
397 {
398 	status_t status = BView::Archive(data, deep);
399 	if (status == B_OK)
400 		status = data->AddString("add_on", TERM_SIGNATURE);
401 	if (status == B_OK)
402 		status = data->AddInt32("encoding", (int32)fEncoding);
403 	if (status == B_OK)
404 		status = data->AddInt32("columns", (int32)fColumns);
405 	if (status == B_OK)
406 		status = data->AddInt32("rows", (int32)fRows);
407 
408 	if (data->ReplaceString("class", "TermView") != B_OK)
409 		data->AddString("class", "TermView");
410 
411 	return status;
412 }
413 
414 
415 inline int32
416 TermView::_LineAt(float y)
417 {
418 	int32 location = int32(y + fScrollOffset);
419 
420 	// Make sure negative offsets are rounded towards the lower neighbor, too.
421 	if (location < 0)
422 		location -= fFontHeight - 1;
423 
424 	return location / fFontHeight;
425 }
426 
427 
428 inline float
429 TermView::_LineOffset(int32 index)
430 {
431 	return index * fFontHeight - fScrollOffset;
432 }
433 
434 
435 // convert view coordinates to terminal text buffer position
436 inline TermPos
437 TermView::_ConvertToTerminal(const BPoint &p)
438 {
439 	return TermPos(p.x >= 0 ? (int32)p.x / fFontWidth : -1, _LineAt(p.y));
440 }
441 
442 
443 // convert terminal text buffer position to view coordinates
444 inline BPoint
445 TermView::_ConvertFromTerminal(const TermPos &pos)
446 {
447 	return BPoint(fFontWidth * pos.x, _LineOffset(pos.y));
448 }
449 
450 
451 inline void
452 TermView::_InvalidateTextRect(int32 x1, int32 y1, int32 x2, int32 y2)
453 {
454 	BRect rect(x1 * fFontWidth, _LineOffset(y1),
455 	    (x2 + 1) * fFontWidth - 1, _LineOffset(y2 + 1) - 1);
456 //debug_printf("Invalidate((%f, %f) - (%f, %f))\n", rect.left, rect.top,
457 //rect.right, rect.bottom);
458 	Invalidate(rect);
459 }
460 
461 
462 void
463 TermView::GetPreferredSize(float *width, float *height)
464 {
465 	if (width)
466 		*width = fColumns * fFontWidth - 1;
467 	if (height)
468 		*height = fRows * fFontHeight - 1;
469 }
470 
471 
472 const char *
473 TermView::TerminalName() const
474 {
475 	if (fShell == NULL)
476 		return NULL;
477 
478 	return fShell->TTYName();
479 }
480 
481 
482 //! Get width and height for terminal font
483 void
484 TermView::GetFontSize(int* _width, int* _height)
485 {
486 	*_width = fFontWidth;
487 	*_height = fFontHeight;
488 }
489 
490 
491 int
492 TermView::Rows() const
493 {
494 	return fRows;
495 }
496 
497 
498 int
499 TermView::Columns() const
500 {
501 	return fColumns;
502 }
503 
504 
505 //! Set number of rows and columns in terminal
506 BRect
507 TermView::SetTermSize(int rows, int columns)
508 {
509 //debug_printf("TermView::SetTermSize(%d, %d)\n", rows, columns);
510 	if (rows > 0)
511 		fRows = rows;
512 	if (columns > 0)
513 		fColumns = columns;
514 
515 	// To keep things simple, get rid of the selection first.
516 	_Deselect();
517 
518 	{
519 		BAutolock _(fTextBuffer);
520 		if (fTextBuffer->ResizeTo(columns, rows) != B_OK
521 			|| fVisibleTextBuffer->ResizeTo(columns, rows + 2, 0)
522 				!= B_OK) {
523 			return Bounds();
524 		}
525 	}
526 
527 //debug_printf("Invalidate()\n");
528 	Invalidate();
529 
530 	if (fScrollBar != NULL) {
531 		_UpdateScrollBarRange();
532 		fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
533 	}
534 
535 	BRect rect(0, 0, fColumns * fFontWidth, fRows * fFontHeight);
536 
537 	// synchronize the visible text buffer
538 	{
539 		BAutolock _(fTextBuffer);
540 
541 		_SynchronizeWithTextBuffer(0, -1);
542 		int32 offset = _LineAt(0);
543 		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset,
544 			offset + rows + 2);
545 	}
546 
547 	return rect;
548 }
549 
550 
551 void
552 TermView::SetTermSize(BRect rect)
553 {
554 	int rows;
555 	int columns;
556 
557 	GetTermSizeFromRect(rect, &rows, &columns);
558 	SetTermSize(rows, columns);
559 }
560 
561 
562 void
563 TermView::GetTermSizeFromRect(const BRect &rect, int *_rows,
564 	int *_columns)
565 {
566 	int columns = (rect.IntegerWidth() + 1) / fFontWidth;
567 	int rows = (rect.IntegerHeight() + 1) / fFontHeight;
568 
569 	if (_rows)
570 		*_rows = rows;
571 	if (_columns)
572 		*_columns = columns;
573 }
574 
575 
576 void
577 TermView::SetTextColor(rgb_color fore, rgb_color back)
578 {
579 	fTextForeColor = fore;
580 	fTextBackColor = back;
581 
582 	SetLowColor(fTextBackColor);
583 }
584 
585 
586 void
587 TermView::SetSelectColor(rgb_color fore, rgb_color back)
588 {
589 	fSelectForeColor = fore;
590 	fSelectBackColor = back;
591 }
592 
593 
594 void
595 TermView::SetCursorColor(rgb_color fore, rgb_color back)
596 {
597 	fCursorForeColor = fore;
598 	fCursorBackColor = back;
599 }
600 
601 
602 int
603 TermView::Encoding() const
604 {
605 	return fEncoding;
606 }
607 
608 
609 void
610 TermView::SetEncoding(int encoding)
611 {
612 	// TODO: Shell::_Spawn() sets the "TTYPE" environment variable using
613 	// the string value of encoding. But when this function is called and
614 	// the encoding changes, the new value is never passed to Shell.
615 	fEncoding = encoding;
616 
617 	BAutolock _(fTextBuffer);
618 	fTextBuffer->SetEncoding(fEncoding);
619 }
620 
621 
622 void
623 TermView::SetMouseClipboard(BClipboard *clipboard)
624 {
625 	fMouseClipboard = clipboard;
626 }
627 
628 
629 void
630 TermView::GetTermFont(BFont *font) const
631 {
632 	if (font != NULL)
633 		*font = fHalfFont;
634 }
635 
636 
637 //! Sets font for terminal
638 void
639 TermView::SetTermFont(const BFont *font)
640 {
641 	int halfWidth = 0;
642 
643 	fHalfFont = font;
644 
645 	fHalfFont.SetSpacing(B_FIXED_SPACING);
646 
647 	// calculate half font's max width
648 	// Not Bounding, check only A-Z(For case of fHalfFont is KanjiFont. )
649 	for (int c = 0x20 ; c <= 0x7e; c++){
650 		char buf[4];
651 		sprintf(buf, "%c", c);
652 		int tmpWidth = (int)fHalfFont.StringWidth(buf);
653 		if (tmpWidth > halfWidth)
654 			halfWidth = tmpWidth;
655 	}
656 
657 	fFontWidth = halfWidth;
658 
659 	font_height hh;
660 	fHalfFont.GetHeight(&hh);
661 
662 	int font_ascent = (int)hh.ascent;
663 	int font_descent =(int)hh.descent;
664 	int font_leading =(int)hh.leading;
665 
666 	if (font_leading == 0)
667 		font_leading = 1;
668 
669 	fFontAscent = font_ascent;
670 	fFontHeight = font_ascent + font_descent + font_leading + 1;
671 
672 	fCursorHeight = fFontHeight;
673 
674 	_ScrollTo(0, false);
675 	if (fScrollBar != NULL)
676 		fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
677 }
678 
679 
680 void
681 TermView::SetScrollBar(BScrollBar *scrollBar)
682 {
683 	fScrollBar = scrollBar;
684 	if (fScrollBar != NULL)
685 		fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
686 }
687 
688 
689 void
690 TermView::SetTitle(const char *title)
691 {
692 	// TODO: Do something different in case we're a replicant,
693 	// or in case we are inside a BTabView ?
694 	if (Window())
695 		Window()->SetTitle(title);
696 }
697 
698 
699 void
700 TermView::Copy(BClipboard *clipboard)
701 {
702 	BAutolock _(fTextBuffer);
703 
704 	if (!_HasSelection())
705 		return;
706 
707 	BString copyStr;
708 	fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd);
709 
710 	if (clipboard->Lock()) {
711 		BMessage *clipMsg = NULL;
712 		clipboard->Clear();
713 
714 		if ((clipMsg = clipboard->Data()) != NULL) {
715 			clipMsg->AddData("text/plain", B_MIME_TYPE, copyStr.String(),
716 				copyStr.Length());
717 			clipboard->Commit();
718 		}
719 		clipboard->Unlock();
720 	}
721 }
722 
723 
724 void
725 TermView::Paste(BClipboard *clipboard)
726 {
727 	if (clipboard->Lock()) {
728 		BMessage *clipMsg = clipboard->Data();
729 		const char* text;
730 		ssize_t numBytes;
731 		if (clipMsg->FindData("text/plain", B_MIME_TYPE,
732 				(const void**)&text, &numBytes) == B_OK ) {
733 			_WritePTY(text, numBytes);
734 		}
735 
736 		clipboard->Unlock();
737 
738 		_ScrollTo(0, true);
739 	}
740 }
741 
742 
743 void
744 TermView::SelectAll()
745 {
746 	BAutolock _(fTextBuffer);
747 
748 	_Select(TermPos(0, -fTextBuffer->HistorySize()),
749 		TermPos(0, fTextBuffer->Height()), false, true);
750 }
751 
752 
753 void
754 TermView::Clear()
755 {
756 	_Deselect();
757 
758 	{
759 		BAutolock _(fTextBuffer);
760 		fTextBuffer->Clear(true);
761 	}
762 	fVisibleTextBuffer->Clear(true);
763 
764 //debug_printf("Invalidate()\n");
765 	Invalidate();
766 
767 	_ScrollTo(0, false);
768 	if (fScrollBar) {
769 		fScrollBar->SetRange(0, 0);
770 		fScrollBar->SetProportion(1);
771 	}
772 }
773 
774 
775 //! Draw region
776 void
777 TermView::_InvalidateTextRange(TermPos start, TermPos end)
778 {
779 	if (end < start)
780 		std::swap(start, end);
781 
782 	if (start.y == end.y) {
783 		_InvalidateTextRect(start.x, start.y, end.x, end.y);
784 	} else {
785 		_InvalidateTextRect(start.x, start.y, fColumns, start.y);
786 
787 		if (end.y - start.y > 0)
788 			_InvalidateTextRect(0, start.y + 1, fColumns, end.y - 1);
789 
790 		_InvalidateTextRect(0, end.y, end.x, end.y);
791 	}
792 }
793 
794 
795 status_t
796 TermView::_AttachShell(Shell *shell)
797 {
798 	if (shell == NULL)
799 		return B_BAD_VALUE;
800 
801 	fShell = shell;
802 
803 	return fShell->AttachBuffer(TextBuffer());
804 }
805 
806 
807 void
808 TermView::_DetachShell()
809 {
810 	fShell->DetachBuffer();
811 	fShell = NULL;
812 }
813 
814 
815 void
816 TermView::_Activate()
817 {
818 	fActive = true;
819 
820 	if (fCursorBlinkRunner == NULL) {
821 		BMessage blinkMessage(kBlinkCursor);
822 		fCursorBlinkRunner = new (std::nothrow) BMessageRunner(
823 			BMessenger(this), &blinkMessage, kCursorBlinkInterval);
824 	}
825 }
826 
827 
828 void
829 TermView::_Deactivate()
830 {
831 	// make sure the cursor becomes visible
832 	fCursorState = 0;
833 	_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
834 	delete fCursorBlinkRunner;
835 	fCursorBlinkRunner = NULL;
836 
837 	fActive = false;
838 }
839 
840 
841 //! Draw part of a line in the given view.
842 void
843 TermView::_DrawLinePart(int32 x1, int32 y1, uint16 attr, char *buf,
844 	int32 width, bool mouse, bool cursor, BView *inView)
845 {
846 	rgb_color rgb_fore = fTextForeColor, rgb_back = fTextBackColor;
847 
848 	inView->SetFont(&fHalfFont);
849 
850 	// Set pen point
851 	int x2 = x1 + fFontWidth * width;
852 	int y2 = y1 + fFontHeight;
853 
854 	// color attribute
855 	int forecolor = IS_FORECOLOR(attr);
856 	int backcolor = IS_BACKCOLOR(attr);
857 
858 	if (IS_FORESET(attr))
859 		rgb_fore = kTermColorTable[forecolor];
860 
861 	if (IS_BACKSET(attr))
862 		rgb_back = kTermColorTable[backcolor];
863 
864 	// Selection check.
865 	if (cursor) {
866 		rgb_fore = fCursorForeColor;
867 		rgb_back = fCursorBackColor;
868 	} else if (mouse) {
869 		rgb_fore = fSelectForeColor;
870 		rgb_back = fSelectBackColor;
871 	} else {
872 		// Reverse attribute(If selected area, don't reverse color).
873 		if (IS_INVERSE(attr)) {
874 			rgb_color rgb_tmp = rgb_fore;
875 			rgb_fore = rgb_back;
876 			rgb_back = rgb_tmp;
877 		}
878 	}
879 
880 	// Fill color at Background color and set low color.
881 	inView->SetHighColor(rgb_back);
882 	inView->FillRect(BRect(x1, y1, x2 - 1, y2 - 1));
883 	inView->SetLowColor(rgb_back);
884 
885 	inView->SetHighColor(rgb_fore);
886 
887 	// Draw character.
888 	inView->MovePenTo(x1, y1 + fFontAscent);
889 	inView->DrawString((char *) buf);
890 
891 	// bold attribute.
892 	if (IS_BOLD(attr)) {
893 		inView->MovePenTo(x1 + 1, y1 + fFontAscent);
894 
895 		inView->SetDrawingMode(B_OP_OVER);
896 		inView->DrawString((char *)buf);
897 		inView->SetDrawingMode(B_OP_COPY);
898 	}
899 
900 	// underline attribute
901 	if (IS_UNDER(attr)) {
902 		inView->MovePenTo(x1, y1 + fFontAscent);
903 		inView->StrokeLine(BPoint(x1 , y1 + fFontAscent),
904 			BPoint(x2 , y1 + fFontAscent));
905 	}
906 }
907 
908 
909 /*!	Caller must have locked fTextBuffer.
910 */
911 void
912 TermView::_DrawCursor()
913 {
914 	BRect rect(fFontWidth * fCursor.x, _LineOffset(fCursor.y), 0, 0);
915 	rect.right = rect.left + fFontWidth - 1;
916 	rect.bottom = rect.top + fCursorHeight - 1;
917 	int32 firstVisible = _LineAt(0);
918 
919 	UTF8Char character;
920 	uint16 attr;
921 
922 	bool cursorVisible = _IsCursorVisible();
923 
924 	bool selected = _CheckSelectedRegion(TermPos(fCursor.x, fCursor.y));
925 	if (fVisibleTextBuffer->GetChar(fCursor.y - firstVisible, fCursor.x,
926 			character, attr) == A_CHAR) {
927 		int32 width;
928 		if (IS_WIDTH(attr))
929 			width = 2;
930 		else
931 			width = 1;
932 
933 		char buffer[5];
934 		int32 bytes = UTF8Char::ByteCount(character.bytes[0]);
935 		memcpy(buffer, character.bytes, bytes);
936 		buffer[bytes] = '\0';
937 
938 		_DrawLinePart(fCursor.x * fFontWidth, (int32)rect.top, attr, buffer,
939 			width, selected, cursorVisible, this);
940 	} else {
941 		if (selected)
942 			SetHighColor(fSelectBackColor);
943 		else
944 			SetHighColor(cursorVisible ? fCursorBackColor : fTextBackColor);
945 
946 		FillRect(rect);
947 	}
948 }
949 
950 
951 bool
952 TermView::_IsCursorVisible() const
953 {
954 	return fCursorState < kCursorVisibleIntervals;
955 }
956 
957 
958 void
959 TermView::_BlinkCursor()
960 {
961 	bool wasVisible = _IsCursorVisible();
962 
963 	if (!wasVisible && fInline && fInline->IsActive())
964 		return;
965 
966 	bigtime_t now = system_time();
967 	if (Window()->IsActive() && now - fLastActivityTime >= kCursorBlinkInterval)
968 		fCursorState = (fCursorState + 1) % kCursorBlinkIntervals;
969 	else
970 		fCursorState = 0;
971 
972 	if (wasVisible != _IsCursorVisible())
973 		_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
974 }
975 
976 
977 void
978 TermView::_ActivateCursor(bool invalidate)
979 {
980 	fLastActivityTime = system_time();
981 	if (invalidate && fCursorState != 0)
982 		_BlinkCursor();
983 	else
984 		fCursorState = 0;
985 }
986 
987 
988 //! Update scroll bar range and knob size.
989 void
990 TermView::_UpdateScrollBarRange()
991 {
992 	if (fScrollBar == NULL)
993 		return;
994 
995 	int32 historySize;
996 	{
997 		BAutolock _(fTextBuffer);
998 		historySize = fTextBuffer->HistorySize();
999 	}
1000 
1001 	float viewHeight = fRows * fFontHeight;
1002 	float historyHeight = (float)historySize * fFontHeight;
1003 
1004 //debug_printf("TermView::_UpdateScrollBarRange(): history: %ld, range: %f - 0\n",
1005 //historySize, -historyHeight);
1006 
1007 	fScrollBar->SetRange(-historyHeight, 0);
1008 	if (historySize > 0)
1009 		fScrollBar->SetProportion(viewHeight / (viewHeight + historyHeight));
1010 }
1011 
1012 
1013 //!	Handler for SIGWINCH
1014 void
1015 TermView::_UpdateSIGWINCH()
1016 {
1017 	if (fFrameResized) {
1018 		fShell->UpdateWindowSize(fRows, fColumns);
1019 		fFrameResized = false;
1020 	}
1021 }
1022 
1023 
1024 void
1025 TermView::AttachedToWindow()
1026 {
1027 	fMouseButtons = 0;
1028 
1029 	MakeFocus(true);
1030 	if (fScrollBar) {
1031 		fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
1032 		_UpdateScrollBarRange();
1033 	}
1034 
1035 	BMessenger thisMessenger(this);
1036 
1037 	BMessage message(kUpdateSigWinch);
1038 	fWinchRunner = new (std::nothrow) BMessageRunner(thisMessenger,
1039 		&message, 500000);
1040 
1041 	{
1042 		BAutolock _(fTextBuffer);
1043 		fTextBuffer->SetListener(thisMessenger);
1044 		_SynchronizeWithTextBuffer(0, -1);
1045 	}
1046 
1047 	be_clipboard->StartWatching(thisMessenger);
1048 }
1049 
1050 
1051 void
1052 TermView::DetachedFromWindow()
1053 {
1054 	be_clipboard->StopWatching(BMessenger(this));
1055 
1056 	delete fWinchRunner;
1057 	fWinchRunner = NULL;
1058 
1059 	delete fCursorBlinkRunner;
1060 	fCursorBlinkRunner = NULL;
1061 
1062 	delete fResizeRunner;
1063 	fResizeRunner = NULL;
1064 
1065 	{
1066 		BAutolock _(fTextBuffer);
1067 		fTextBuffer->UnsetListener();
1068 	}
1069 }
1070 
1071 
1072 void
1073 TermView::Draw(BRect updateRect)
1074 {
1075 //	if (IsPrinting()) {
1076 //		_DoPrint(updateRect);
1077 //		return;
1078 //	}
1079 
1080 // debug_printf("TermView::Draw((%f, %f) - (%f, %f))\n", updateRect.left,
1081 // updateRect.top, updateRect.right, updateRect.bottom);
1082 // {
1083 // BRect bounds(Bounds());
1084 // debug_printf("Bounds(): (%f, %f) - (%f - %f)\n", bounds.left, bounds.top,
1085 // 	bounds.right, bounds.bottom);
1086 // debug_printf("clipping region:\n");
1087 // BRegion region;
1088 // GetClippingRegion(&region);
1089 // for (int32 i = 0; i < region.CountRects(); i++) {
1090 // 	BRect rect(region.RectAt(i));
1091 // 	debug_printf("  (%f, %f) - (%f, %f)\n", rect.left, rect.top, rect.right,
1092 // 		rect.bottom);
1093 // }
1094 // }
1095 
1096 	int32 x1 = (int32)updateRect.left / fFontWidth;
1097 	int32 x2 = std::min((int)updateRect.right / fFontWidth, fColumns - 1);
1098 
1099 	int32 firstVisible = _LineAt(0);
1100 	int32 y1 = _LineAt(updateRect.top);
1101 	int32 y2 = std::min(_LineAt(updateRect.bottom), (int32)fRows - 1);
1102 
1103 //debug_printf("TermView::Draw(): (%ld, %ld) - (%ld, %ld), top: %f, fontHeight: %d, scrollOffset: %f\n",
1104 //x1, y1, x2, y2, updateRect.top, fFontHeight, fScrollOffset);
1105 
1106 	// clear the area to the right of the line ends
1107 	if (y1 <= y2) {
1108 		float clearLeft = fColumns * fFontWidth;
1109 		if (clearLeft <= updateRect.right) {
1110 			BRect rect(clearLeft, updateRect.top, updateRect.right,
1111 				updateRect.bottom);
1112 			SetHighColor(fTextBackColor);
1113 			FillRect(rect);
1114 		}
1115 	}
1116 
1117 	// clear the area below the last line
1118 	if (y2 == fRows - 1) {
1119 		float clearTop = _LineOffset(fRows);
1120 		if (clearTop <= updateRect.bottom) {
1121 			BRect rect(updateRect.left, clearTop, updateRect.right,
1122 				updateRect.bottom);
1123 			SetHighColor(fTextBackColor);
1124 			FillRect(rect);
1125 		}
1126 	}
1127 
1128 	// draw the affected line parts
1129 	if (x1 <= x2) {
1130 		for (int32 j = y1; j <= y2; j++) {
1131 			int32 k = x1;
1132 			char buf[fColumns * 4 + 1];
1133 
1134 			if (fVisibleTextBuffer->IsFullWidthChar(j - firstVisible, k))
1135 				k--;
1136 
1137 			if (k < 0)
1138 				k = 0;
1139 
1140 			for (int32 i = k; i <= x2;) {
1141 				int32 lastColumn = x2;
1142 				bool insideSelection = _CheckSelectedRegion(j, i, lastColumn);
1143 				uint16 attr;
1144 				int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i,
1145 					lastColumn, buf, attr);
1146 
1147 //debug_printf("  fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), selected: %d\n",
1148 //j - firstVisible, i, lastColumn, count, (int)count, buf, insideSelection);
1149 
1150 				if (count == 0) {
1151 					BRect rect(fFontWidth * i, _LineOffset(j),
1152 						fFontWidth * (lastColumn + 1) - 1, 0);
1153 					rect.bottom = rect.top + fFontHeight - 1;
1154 
1155 					SetHighColor(insideSelection ? fSelectBackColor
1156 						: fTextBackColor);
1157 					FillRect(rect);
1158 
1159 					i = lastColumn + 1;
1160 					continue;
1161 				}
1162 
1163 				if (IS_WIDTH(attr))
1164 					count = 2;
1165 
1166 				_DrawLinePart(fFontWidth * i, (int32)_LineOffset(j),
1167 					attr, buf, count, insideSelection, false, this);
1168 				i += count;
1169 			}
1170 		}
1171 	}
1172 
1173 	if (fInline && fInline->IsActive())
1174 		_DrawInlineMethodString();
1175 
1176 	if (fCursor >= TermPos(x1, y1) && fCursor <= TermPos(x2, y2))
1177 		_DrawCursor();
1178 }
1179 
1180 
1181 void
1182 TermView::_DoPrint(BRect updateRect)
1183 {
1184 #if 0
1185 	ushort attr;
1186 	uchar buf[1024];
1187 
1188 	const int numLines = (int)((updateRect.Height()) / fFontHeight);
1189 
1190 	int y1 = (int)(updateRect.top) / fFontHeight;
1191 	y1 = y1 -(fScrBufSize - numLines * 2);
1192 	if (y1 < 0)
1193 		y1 = 0;
1194 
1195 	const int y2 = y1 + numLines -1;
1196 
1197 	const int x1 = (int)(updateRect.left) / fFontWidth;
1198 	const int x2 = (int)(updateRect.right) / fFontWidth;
1199 
1200 	for (int j = y1; j <= y2; j++) {
1201 		// If(x1, y1) Buffer is in string full width character,
1202 		// alignment start position.
1203 
1204 		int k = x1;
1205 		if (fTextBuffer->IsFullWidthChar(j, k))
1206 			k--;
1207 
1208 		if (k < 0)
1209 			k = 0;
1210 
1211 		for (int i = k; i <= x2;) {
1212 			int count = fTextBuffer->GetString(j, i, x2, buf, &attr);
1213 			if (count < 0) {
1214 				i += abs(count);
1215 				continue;
1216 			}
1217 
1218 			_DrawLinePart(fFontWidth * i, fFontHeight * j,
1219 				attr, buf, count, false, false, this);
1220 			i += count;
1221 		}
1222 	}
1223 #endif	// 0
1224 }
1225 
1226 
1227 void
1228 TermView::WindowActivated(bool active)
1229 {
1230 	BView::WindowActivated(active);
1231 	if (active && IsFocus()) {
1232 		if (!fActive)
1233 			_Activate();
1234 	} else {
1235 		if (fActive)
1236 			_Deactivate();
1237 	}
1238 }
1239 
1240 
1241 void
1242 TermView::MakeFocus(bool focusState)
1243 {
1244 	BView::MakeFocus(focusState);
1245 
1246 	if (focusState && Window() && Window()->IsActive()) {
1247 		if (!fActive)
1248 			_Activate();
1249 	} else {
1250 		if (fActive)
1251 			_Deactivate();
1252 	}
1253 }
1254 
1255 
1256 void
1257 TermView::KeyDown(const char *bytes, int32 numBytes)
1258 {
1259 	int32 key, mod, rawChar;
1260 	BMessage *currentMessage = Looper()->CurrentMessage();
1261 	if (currentMessage == NULL)
1262 		return;
1263 
1264 	currentMessage->FindInt32("modifiers", &mod);
1265 	currentMessage->FindInt32("key", &key);
1266 	currentMessage->FindInt32("raw_char", &rawChar);
1267 
1268 	_ActivateCursor(true);
1269 
1270 	// handle multi-byte chars
1271 	if (numBytes > 1) {
1272 		if (fEncoding != M_UTF8) {
1273 			char destBuffer[16];
1274 			int32 destLen;
1275 			long state = 0;
1276 			convert_from_utf8(fEncoding, bytes, &numBytes, destBuffer,
1277 				&destLen, &state, '?');
1278 			_ScrollTo(0, true);
1279 			fShell->Write(destBuffer, destLen);
1280 			return;
1281 		}
1282 
1283 		_ScrollTo(0, true);
1284 		fShell->Write(bytes, numBytes);
1285 		return;
1286 	}
1287 
1288 	// Terminal filters RET, ENTER, F1...F12, and ARROW key code.
1289 	const char *toWrite = NULL;
1290 
1291 	switch (*bytes) {
1292 		case B_RETURN:
1293 			if (rawChar == B_RETURN)
1294 				toWrite = "\r";
1295 			break;
1296 
1297 		case B_DELETE:
1298 			toWrite = DELETE_KEY_CODE;
1299 			break;
1300 
1301 		case B_BACKSPACE:
1302 			// Translate only the actual backspace key to the backspace
1303 			// code. CTRL-H shall just be echoed.
1304 			if (!((mod & B_CONTROL_KEY) && rawChar == 'h'))
1305 				toWrite = BACKSPACE_KEY_CODE;
1306 			break;
1307 
1308 		case B_LEFT_ARROW:
1309 			if (rawChar == B_LEFT_ARROW) {
1310 				if (mod & B_SHIFT_KEY) {
1311 					BMessage message(MSG_PREVIOUS_TAB);
1312 					message.AddPointer("termView", this);
1313 					Window()->PostMessage(&message);
1314 					return;
1315 				} else if ((mod & B_CONTROL_KEY) || (mod & B_COMMAND_KEY)) {
1316 					toWrite = CTRL_LEFT_ARROW_KEY_CODE;
1317 				} else
1318 					toWrite = LEFT_ARROW_KEY_CODE;
1319 			}
1320 			break;
1321 
1322 		case B_RIGHT_ARROW:
1323 			if (rawChar == B_RIGHT_ARROW) {
1324 				if (mod & B_SHIFT_KEY) {
1325 					BMessage message(MSG_NEXT_TAB);
1326 					message.AddPointer("termView", this);
1327 					Window()->PostMessage(&message);
1328 					return;
1329 				} else if ((mod & B_CONTROL_KEY) || (mod & B_COMMAND_KEY)) {
1330 					toWrite = CTRL_RIGHT_ARROW_KEY_CODE;
1331 				} else
1332 					toWrite = RIGHT_ARROW_KEY_CODE;
1333 			}
1334 			break;
1335 
1336 		case B_UP_ARROW:
1337 			if (mod & B_SHIFT_KEY) {
1338 				_ScrollTo(fScrollOffset - fFontHeight, true);
1339 				return;
1340 			}
1341 			if (rawChar == B_UP_ARROW) {
1342 				if (mod & B_CONTROL_KEY)
1343 					toWrite = CTRL_UP_ARROW_KEY_CODE;
1344 				else
1345 					toWrite = UP_ARROW_KEY_CODE;
1346 			}
1347 			break;
1348 
1349 		case B_DOWN_ARROW:
1350 			if (mod & B_SHIFT_KEY) {
1351 				_ScrollTo(fScrollOffset + fFontHeight, true);
1352 				return;
1353 			}
1354 
1355 			if (rawChar == B_DOWN_ARROW) {
1356 				if (mod & B_CONTROL_KEY)
1357 					toWrite = CTRL_DOWN_ARROW_KEY_CODE;
1358 				else
1359 					toWrite = DOWN_ARROW_KEY_CODE;
1360 			}
1361 			break;
1362 
1363 		case B_INSERT:
1364 			if (rawChar == B_INSERT)
1365 				toWrite = INSERT_KEY_CODE;
1366 			break;
1367 
1368 		case B_HOME:
1369 			if (rawChar == B_HOME)
1370 				toWrite = HOME_KEY_CODE;
1371 			break;
1372 
1373 		case B_END:
1374 			if (rawChar == B_END)
1375 				toWrite = END_KEY_CODE;
1376 			break;
1377 
1378 		case B_PAGE_UP:
1379 			if (mod & B_SHIFT_KEY) {
1380 				_ScrollTo(fScrollOffset - fFontHeight  * fRows, true);
1381 				return;
1382 			}
1383 			if (rawChar == B_PAGE_UP)
1384 				toWrite = PAGE_UP_KEY_CODE;
1385 			break;
1386 
1387 		case B_PAGE_DOWN:
1388 			if (mod & B_SHIFT_KEY) {
1389 				_ScrollTo(fScrollOffset + fFontHeight * fRows, true);
1390 				return;
1391 			}
1392 			if (rawChar == B_PAGE_DOWN)
1393 				toWrite = PAGE_DOWN_KEY_CODE;
1394 			break;
1395 
1396 		case B_FUNCTION_KEY:
1397 			for (int32 i = 0; i < 12; i++) {
1398 				if (key == function_keycode_table[i]) {
1399 					toWrite = function_key_char_table[i];
1400 					break;
1401 				}
1402 			}
1403 			break;
1404 	}
1405 
1406 	// If the above code proposed an alternative string to write, we get it's
1407 	// length. Otherwise we write exactly the bytes passed to this method.
1408 	size_t toWriteLen;
1409 	if (toWrite != NULL) {
1410 		toWriteLen = strlen(toWrite);
1411 	} else {
1412 		toWrite = bytes;
1413 		toWriteLen = numBytes;
1414 	}
1415 
1416 	_ScrollTo(0, true);
1417 	fShell->Write(toWrite, toWriteLen);
1418 }
1419 
1420 
1421 void
1422 TermView::FrameResized(float width, float height)
1423 {
1424 //debug_printf("TermView::FrameResized(%f, %f)\n", width, height);
1425 	int32 columns = ((int32)width + 1) / fFontWidth;
1426 	int32 rows = ((int32)height + 1) / fFontHeight;
1427 
1428 	if (columns == fColumns && rows == fRows)
1429 		return;
1430 
1431 	bool hasResizeView = fResizeRunner != NULL;
1432 	if (!hasResizeView) {
1433 		// show the current size in a view
1434 		fResizeView = new BStringView(BRect(100, 100, 300, 140),
1435 			B_TRANSLATE("size"), "");
1436 		fResizeView->SetAlignment(B_ALIGN_CENTER);
1437 		fResizeView->SetFont(be_bold_font);
1438 
1439 		BMessage message(MSG_REMOVE_RESIZE_VIEW_IF_NEEDED);
1440 		fResizeRunner = new(std::nothrow) BMessageRunner(BMessenger(this),
1441 			&message, 25000LL);
1442 	}
1443 
1444 	BString text;
1445 	text << columns << " x " << rows;
1446 	fResizeView->SetText(text.String());
1447 	fResizeView->GetPreferredSize(&width, &height);
1448 	fResizeView->ResizeTo(width * 1.5, height * 1.5);
1449 	fResizeView->MoveTo((Bounds().Width() - fResizeView->Bounds().Width()) / 2,
1450 		(Bounds().Height()- fResizeView->Bounds().Height()) / 2);
1451 	if (!hasResizeView && fResizeViewDisableCount < 1)
1452 		AddChild(fResizeView);
1453 
1454 	if (fResizeViewDisableCount > 0)
1455 		fResizeViewDisableCount--;
1456 
1457 	SetTermSize(rows, columns);
1458 
1459 	fFrameResized = true;
1460 }
1461 
1462 
1463 void
1464 TermView::MessageReceived(BMessage *msg)
1465 {
1466 	entry_ref ref;
1467 	const char *ctrl_l = "\x0c";
1468 
1469 	// first check for any dropped message
1470 	if (msg->WasDropped() && (msg->what == B_SIMPLE_DATA
1471 			|| msg->what == B_MIME_DATA)) {
1472 		char *text;
1473 		int32 numBytes;
1474 		//rgb_color *color;
1475 
1476 		int32 i = 0;
1477 
1478 		if (msg->FindRef("refs", i++, &ref) == B_OK) {
1479 			// first check if secondary mouse button is pressed
1480 			int32 buttons = 0;
1481 			msg->FindInt32("buttons", &buttons);
1482 
1483 			if (buttons == B_SECONDARY_MOUSE_BUTTON) {
1484 				// start popup menu
1485 				_SecondaryMouseButtonDropped(msg);
1486 				return;
1487 			}
1488 
1489 			_DoFileDrop(ref);
1490 
1491 			while (msg->FindRef("refs", i++, &ref) == B_OK) {
1492 				_WritePTY(" ", 1);
1493 				_DoFileDrop(ref);
1494 			}
1495 			return;
1496 #if 0
1497 		} else if (msg->FindData("RGBColor", B_RGB_COLOR_TYPE,
1498 				(const void **)&color, &numBytes) == B_OK
1499 				 && numBytes == sizeof(color)) {
1500 			// TODO: handle color drop
1501 			// maybe only on replicants ?
1502 			return;
1503 #endif
1504 		} else if (msg->FindData("text/plain", B_MIME_TYPE,
1505 			 	(const void **)&text, &numBytes) == B_OK) {
1506 			_WritePTY(text, numBytes);
1507 			return;
1508 		}
1509 	}
1510 
1511 	switch (msg->what){
1512 		case B_ABOUT_REQUESTED:
1513 			// (replicant) about box requested
1514 			AboutRequested();
1515 			break;
1516 
1517 		case B_SIMPLE_DATA:
1518 		case B_REFS_RECEIVED:
1519 		{
1520 			// handle refs if they weren't dropped
1521 			int32 i = 0;
1522 			if (msg->FindRef("refs", i++, &ref) == B_OK) {
1523 				_DoFileDrop(ref);
1524 
1525 				while (msg->FindRef("refs", i++, &ref) == B_OK) {
1526 					_WritePTY(" ", 1);
1527 					_DoFileDrop(ref);
1528 				}
1529 			} else
1530 				BView::MessageReceived(msg);
1531 			break;
1532 		}
1533 
1534 		case B_COPY:
1535 			Copy(be_clipboard);
1536 			break;
1537 
1538 		case B_PASTE:
1539 		{
1540 			int32 code;
1541 			if (msg->FindInt32("index", &code) == B_OK)
1542 				Paste(be_clipboard);
1543 			break;
1544 		}
1545 
1546 		case B_CLIPBOARD_CHANGED:
1547 			// This message originates from the system clipboard. Overwrite
1548 			// the contents of the mouse clipboard with the ones from the
1549 			// system clipboard, in case it contains text data.
1550 			if (be_clipboard->Lock()) {
1551 				if (fMouseClipboard->Lock()) {
1552 					BMessage* clipMsgA = be_clipboard->Data();
1553 					const char* text;
1554 					ssize_t numBytes;
1555 					if (clipMsgA->FindData("text/plain", B_MIME_TYPE,
1556 							(const void**)&text, &numBytes) == B_OK ) {
1557 						fMouseClipboard->Clear();
1558 						BMessage* clipMsgB = fMouseClipboard->Data();
1559 						clipMsgB->AddData("text/plain", B_MIME_TYPE,
1560 							text, numBytes);
1561 						fMouseClipboard->Commit();
1562 					}
1563 					fMouseClipboard->Unlock();
1564 				}
1565 				be_clipboard->Unlock();
1566 			}
1567 			break;
1568 
1569 		case B_SELECT_ALL:
1570 			SelectAll();
1571 			break;
1572 
1573 		case B_SET_PROPERTY:
1574 		{
1575 			int32 i;
1576 			int32 encodingID;
1577 			BMessage specifier;
1578 			msg->GetCurrentSpecifier(&i, &specifier);
1579 			if (!strcmp("encoding", specifier.FindString("property", i))){
1580 				msg->FindInt32 ("data", &encodingID);
1581 				SetEncoding(encodingID);
1582 				msg->SendReply(B_REPLY);
1583 			} else {
1584 				BView::MessageReceived(msg);
1585 			}
1586 			break;
1587 		}
1588 
1589 		case B_GET_PROPERTY:
1590 		{
1591 			int32 i;
1592 			BMessage specifier;
1593 			msg->GetCurrentSpecifier(&i, &specifier);
1594 			if (!strcmp("encoding", specifier.FindString("property", i))){
1595 				BMessage reply(B_REPLY);
1596 				reply.AddInt32("result", Encoding());
1597 				msg->SendReply(&reply);
1598 			} else if (!strcmp("tty", specifier.FindString("property", i))) {
1599 				BMessage reply(B_REPLY);
1600 				reply.AddString("result", TerminalName());
1601 				msg->SendReply(&reply);
1602 			} else {
1603 				BView::MessageReceived(msg);
1604 			}
1605 			break;
1606 		}
1607 
1608 		case B_INPUT_METHOD_EVENT:
1609 		{
1610 			int32 opcode;
1611 			if (msg->FindInt32("be:opcode", &opcode) == B_OK) {
1612 				switch (opcode) {
1613 					case B_INPUT_METHOD_STARTED:
1614 					{
1615 						BMessenger messenger;
1616 						if (msg->FindMessenger("be:reply_to",
1617 								&messenger) == B_OK) {
1618 							fInline = new (std::nothrow)
1619 								InlineInput(messenger);
1620 						}
1621 						break;
1622 					}
1623 
1624 					case B_INPUT_METHOD_STOPPED:
1625 						delete fInline;
1626 						fInline = NULL;
1627 						break;
1628 
1629 					case B_INPUT_METHOD_CHANGED:
1630 						if (fInline != NULL)
1631 							_HandleInputMethodChanged(msg);
1632 						break;
1633 
1634 					case B_INPUT_METHOD_LOCATION_REQUEST:
1635 						if (fInline != NULL)
1636 							_HandleInputMethodLocationRequest();
1637 						break;
1638 
1639 					default:
1640 						break;
1641 				}
1642 			}
1643 			break;
1644 		}
1645 
1646 		case MENU_CLEAR_ALL:
1647 			Clear();
1648 			fShell->Write(ctrl_l, 1);
1649 			break;
1650 		case kBlinkCursor:
1651 			_BlinkCursor();
1652 			break;
1653 		case kUpdateSigWinch:
1654 			_UpdateSIGWINCH();
1655 			break;
1656 		case kAutoScroll:
1657 			_AutoScrollUpdate();
1658 			break;
1659 		case kSecondaryMouseDropAction:
1660 			_DoSecondaryMouseDropAction(msg);
1661 			break;
1662 		case MSG_TERMINAL_BUFFER_CHANGED:
1663 		{
1664 			BAutolock _(fTextBuffer);
1665 			_SynchronizeWithTextBuffer(0, -1);
1666 			break;
1667 		}
1668 		case MSG_SET_TERMNAL_TITLE:
1669 		{
1670 			const char* title;
1671 			if (msg->FindString("title", &title) == B_OK)
1672 				SetTitle(title);
1673 			break;
1674 		}
1675 		case MSG_REPORT_MOUSE_EVENT:
1676 		{
1677 			bool report;
1678 			if (msg->FindBool("reportX10MouseEvent", &report) == B_OK)
1679 				fReportX10MouseEvent = report;
1680 
1681 			if (msg->FindBool("reportNormalMouseEvent", &report) == B_OK)
1682 				fReportNormalMouseEvent = report;
1683 
1684 			if (msg->FindBool("reportButtonMouseEvent", &report) == B_OK)
1685 				fReportButtonMouseEvent = report;
1686 
1687 			if (msg->FindBool("reportAnyMouseEvent", &report) == B_OK)
1688 				fReportAnyMouseEvent = report;
1689 			break;
1690 		}
1691 		case MSG_REMOVE_RESIZE_VIEW_IF_NEEDED:
1692 		{
1693 			BPoint point;
1694 			uint32 buttons;
1695 			GetMouse(&point, &buttons, false);
1696 			if (buttons != 0)
1697 				break;
1698 
1699 			if (fResizeView != NULL) {
1700 				fResizeView->RemoveSelf();
1701 				delete fResizeView;
1702 				fResizeView = NULL;
1703 			}
1704 			delete fResizeRunner;
1705 			fResizeRunner = NULL;
1706 			break;
1707 		}
1708 
1709 		case MSG_QUIT_TERMNAL:
1710 		{
1711 			int32 reason;
1712 			if (msg->FindInt32("reason", &reason) != B_OK)
1713 				reason = 0;
1714 			NotifyQuit(reason);
1715 			break;
1716 		}
1717 		default:
1718 			BView::MessageReceived(msg);
1719 			break;
1720 	}
1721 }
1722 
1723 
1724 status_t
1725 TermView::GetSupportedSuites(BMessage *message)
1726 {
1727 	BPropertyInfo propInfo(sPropList);
1728 	message->AddString("suites", "suite/vnd.naan-termview");
1729 	message->AddFlat("messages", &propInfo);
1730 	return BView::GetSupportedSuites(message);
1731 }
1732 
1733 
1734 void
1735 TermView::ScrollTo(BPoint where)
1736 {
1737 //debug_printf("TermView::ScrollTo(): %f -> %f\n", fScrollOffset, where.y);
1738 	float diff = where.y - fScrollOffset;
1739 	if (diff == 0)
1740 		return;
1741 
1742 	float bottom = Bounds().bottom;
1743 	int32 oldFirstLine = _LineAt(0);
1744 	int32 oldLastLine = _LineAt(bottom);
1745 	int32 newFirstLine = _LineAt(diff);
1746 	int32 newLastLine = _LineAt(bottom + diff);
1747 
1748 	fScrollOffset = where.y;
1749 
1750 	// invalidate the current cursor position before scrolling
1751 	_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
1752 
1753 	// scroll contents
1754 	BRect destRect(Frame().OffsetToCopy(Bounds().LeftTop()));
1755 	BRect sourceRect(destRect.OffsetByCopy(0, diff));
1756 //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
1757 //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
1758 //destRect.left, destRect.top, destRect.right, destRect.bottom);
1759 	CopyBits(sourceRect, destRect);
1760 
1761 	// sync visible text buffer with text buffer
1762 	if (newFirstLine != oldFirstLine || newLastLine != oldLastLine) {
1763 		if (newFirstLine != oldFirstLine)
1764 {
1765 //debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine);
1766 			fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine);
1767 }
1768 		BAutolock _(fTextBuffer);
1769 		if (diff < 0)
1770 			_SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1);
1771 		else
1772 			_SynchronizeWithTextBuffer(oldLastLine + 1, newLastLine);
1773 	}
1774 }
1775 
1776 
1777 void
1778 TermView::TargetedByScrollView(BScrollView *scrollView)
1779 {
1780 	BView::TargetedByScrollView(scrollView);
1781 
1782 	SetScrollBar(scrollView ? scrollView->ScrollBar(B_VERTICAL) : NULL);
1783 }
1784 
1785 
1786 BHandler*
1787 TermView::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
1788 	int32 what, const char* property)
1789 {
1790 	BHandler* target = this;
1791 	BPropertyInfo propInfo(sPropList);
1792 	if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) {
1793 		target = BView::ResolveSpecifier(message, index, specifier, what,
1794 			property);
1795 	}
1796 
1797 	return target;
1798 }
1799 
1800 
1801 void
1802 TermView::_SecondaryMouseButtonDropped(BMessage* msg)
1803 {
1804 	// Launch menu to choose what is to do with the msg data
1805 	BPoint point;
1806 	if (msg->FindPoint("_drop_point_", &point) != B_OK)
1807 		return;
1808 
1809 	BMessage* insertMessage = new BMessage(*msg);
1810 	insertMessage->what = kSecondaryMouseDropAction;
1811 	insertMessage->AddInt8("action", kInsert);
1812 
1813 	BMessage* cdMessage = new BMessage(*msg);
1814 	cdMessage->what = kSecondaryMouseDropAction;
1815 	cdMessage->AddInt8("action", kChangeDirectory);
1816 
1817 	BMessage* lnMessage = new BMessage(*msg);
1818 	lnMessage->what = kSecondaryMouseDropAction;
1819 	lnMessage->AddInt8("action", kLinkFiles);
1820 
1821 	BMessage* mvMessage = new BMessage(*msg);
1822 	mvMessage->what = kSecondaryMouseDropAction;
1823 	mvMessage->AddInt8("action", kMoveFiles);
1824 
1825 	BMessage* cpMessage = new BMessage(*msg);
1826 	cpMessage->what = kSecondaryMouseDropAction;
1827 	cpMessage->AddInt8("action", kCopyFiles);
1828 
1829 	BMenuItem* insertItem = new BMenuItem(
1830 		B_TRANSLATE("Insert path"), insertMessage);
1831 	BMenuItem* cdItem = new BMenuItem(
1832 		B_TRANSLATE("Change directory"), cdMessage);
1833 	BMenuItem* lnItem = new BMenuItem(
1834 		B_TRANSLATE("Create link here"), lnMessage);
1835 	BMenuItem* mvItem = new BMenuItem(B_TRANSLATE("Move here"), mvMessage);
1836 	BMenuItem* cpItem = new BMenuItem(B_TRANSLATE("Copy here"), cpMessage);
1837 	BMenuItem* chItem = new BMenuItem(B_TRANSLATE("Cancel"), NULL);
1838 
1839 	// if the refs point to different directorys disable the cd menu item
1840 	bool differentDirs = false;
1841 	BDirectory firstDir;
1842 	entry_ref ref;
1843 	int i = 0;
1844 	while (msg->FindRef("refs", i++, &ref) == B_OK) {
1845 		BNode node(&ref);
1846 		BEntry entry(&ref);
1847 		BDirectory dir;
1848 		if (node.IsDirectory())
1849 			dir.SetTo(&ref);
1850 		else
1851 			entry.GetParent(&dir);
1852 
1853 		if (i == 1) {
1854 			node_ref nodeRef;
1855 			dir.GetNodeRef(&nodeRef);
1856 			firstDir.SetTo(&nodeRef);
1857 		} else if (firstDir != dir) {
1858 			differentDirs = true;
1859 			break;
1860 		}
1861 	}
1862 	if (differentDirs)
1863 		cdItem->SetEnabled(false);
1864 
1865 	BPopUpMenu *menu = new BPopUpMenu(
1866 		B_TRANSLATE("Secondary mouse button drop menu"));
1867 	menu->SetAsyncAutoDestruct(true);
1868 	menu->AddItem(insertItem);
1869 	menu->AddSeparatorItem();
1870 	menu->AddItem(cdItem);
1871 	menu->AddItem(lnItem);
1872 	menu->AddItem(mvItem);
1873 	menu->AddItem(cpItem);
1874 	menu->AddSeparatorItem();
1875 	menu->AddItem(chItem);
1876 	menu->SetTargetForItems(this);
1877 	menu->Go(point, true, true, true);
1878 }
1879 
1880 
1881 void
1882 TermView::_DoSecondaryMouseDropAction(BMessage* msg)
1883 {
1884 	int8 action = -1;
1885 	msg->FindInt8("action", &action);
1886 
1887 	BString outString = "";
1888 	BString itemString = "";
1889 
1890 	switch (action) {
1891 		case kInsert:
1892 			break;
1893 		case kChangeDirectory:
1894 			outString = "cd ";
1895 			break;
1896 		case kLinkFiles:
1897 			outString = "ln -s ";
1898 			break;
1899 		case kMoveFiles:
1900 			outString = "mv ";
1901 			break;
1902 		case kCopyFiles:
1903 			outString = "cp ";
1904 			break;
1905 
1906 		default:
1907 			return;
1908 	}
1909 
1910 	bool listContainsDirectory = false;
1911 	entry_ref ref;
1912 	int32 i = 0;
1913 	while (msg->FindRef("refs", i++, &ref) == B_OK) {
1914 		BEntry ent(&ref);
1915 		BNode node(&ref);
1916 		BPath path(&ent);
1917 		BString string(path.Path());
1918 
1919 		if (node.IsDirectory())
1920 			listContainsDirectory = true;
1921 
1922 		if (i > 1)
1923 			itemString += " ";
1924 
1925 		if (action == kChangeDirectory) {
1926 			if (!node.IsDirectory()) {
1927 				int32 slash = string.FindLast("/");
1928 				string.Truncate(slash);
1929 			}
1930 			string.CharacterEscape(kEscapeCharacters, '\\');
1931 			itemString += string;
1932 			break;
1933 		}
1934 		string.CharacterEscape(kEscapeCharacters, '\\');
1935 		itemString += string;
1936 	}
1937 
1938 	if (listContainsDirectory && action == kCopyFiles)
1939 		outString += "-R ";
1940 
1941 	outString += itemString;
1942 
1943 	if (action == kLinkFiles || action == kMoveFiles || action == kCopyFiles)
1944 		outString += " .";
1945 
1946 	if (action != kInsert)
1947 		outString += "\n";
1948 
1949 	_WritePTY(outString.String(), outString.Length());
1950 }
1951 
1952 
1953 //! Gets dropped file full path and display it at cursor position.
1954 void
1955 TermView::_DoFileDrop(entry_ref& ref)
1956 {
1957 	BEntry ent(&ref);
1958 	BPath path(&ent);
1959 	BString string(path.Path());
1960 
1961 	string.CharacterEscape(kEscapeCharacters, '\\');
1962 	_WritePTY(string.String(), string.Length());
1963 }
1964 
1965 
1966 /*!	Text buffer must already be locked.
1967 */
1968 void
1969 TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
1970 	int32 visibleDirtyBottom)
1971 {
1972 	TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo();
1973 	int32 linesScrolled = info.linesScrolled;
1974 
1975 //debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, "
1976 //"scrolled: %ld, visible dirty: %ld - %ld\n", info.dirtyTop, info.dirtyBottom,
1977 //info.linesScrolled, visibleDirtyTop, visibleDirtyBottom);
1978 
1979 	bigtime_t now = system_time();
1980 	bigtime_t timeElapsed = now - fLastSyncTime;
1981 	if (timeElapsed > 2 * kSyncUpdateGranularity) {
1982 		// last sync was ages ago
1983 		fLastSyncTime = now;
1984 		fScrolledSinceLastSync = linesScrolled;
1985 	}
1986 
1987 	if (fSyncRunner == NULL) {
1988 		// We consider clocked syncing when more than a full screen height has
1989 		// been scrolled in less than a sync update period. Once we're
1990 		// actively considering it, the same condition will convince us to
1991 		// actually do it.
1992 		if (fScrolledSinceLastSync + linesScrolled <= fRows) {
1993 			// Condition doesn't hold yet. Reset if time is up, or otherwise
1994 			// keep counting.
1995 			if (timeElapsed > kSyncUpdateGranularity) {
1996 				fConsiderClockedSync = false;
1997 				fLastSyncTime = now;
1998 				fScrolledSinceLastSync = linesScrolled;
1999 			} else
2000 				fScrolledSinceLastSync += linesScrolled;
2001 		} else if (fConsiderClockedSync) {
2002 			// We are convinced -- create the sync runner.
2003 			fLastSyncTime = now;
2004 			fScrolledSinceLastSync = 0;
2005 
2006 			BMessage message(MSG_TERMINAL_BUFFER_CHANGED);
2007 			fSyncRunner = new(std::nothrow) BMessageRunner(BMessenger(this),
2008 				&message, kSyncUpdateGranularity);
2009 			if (fSyncRunner != NULL && fSyncRunner->InitCheck() == B_OK)
2010 				return;
2011 
2012 			delete fSyncRunner;
2013 			fSyncRunner = NULL;
2014 		} else {
2015 			// Looks interesting so far. Reset the counts and consider clocked
2016 			// syncing.
2017 			fConsiderClockedSync = true;
2018 			fLastSyncTime = now;
2019 			fScrolledSinceLastSync = 0;
2020 		}
2021 	} else if (timeElapsed < kSyncUpdateGranularity) {
2022 		// sync time not passed yet -- keep counting
2023 		fScrolledSinceLastSync += linesScrolled;
2024 		return;
2025 	} else if (fScrolledSinceLastSync + linesScrolled <= fRows) {
2026 		// time's up, but not enough happened
2027 		delete fSyncRunner;
2028 		fSyncRunner = NULL;
2029 		fLastSyncTime = now;
2030 		fScrolledSinceLastSync = linesScrolled;
2031 	} else {
2032 		// Things are still rolling, but the sync time's up.
2033 		fLastSyncTime = now;
2034 		fScrolledSinceLastSync = 0;
2035 	}
2036 
2037 	// Simple case first -- complete invalidation.
2038 	if (info.invalidateAll) {
2039 		Invalidate();
2040 		_UpdateScrollBarRange();
2041 		_Deselect();
2042 
2043 		fCursor = fTextBuffer->Cursor();
2044 		_ActivateCursor(false);
2045 
2046 		int32 offset = _LineAt(0);
2047 		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset,
2048 			offset + fTextBuffer->Height() + 2);
2049 
2050 		info.Reset();
2051 		return;
2052 	}
2053 
2054 	BRect bounds = Bounds();
2055 	int32 firstVisible = _LineAt(0);
2056 	int32 lastVisible = _LineAt(bounds.bottom);
2057 	int32 historySize = fTextBuffer->HistorySize();
2058 
2059 	bool doScroll = false;
2060 	if (linesScrolled > 0) {
2061 		_UpdateScrollBarRange();
2062 
2063 		visibleDirtyTop -= linesScrolled;
2064 		visibleDirtyBottom -= linesScrolled;
2065 
2066 		if (firstVisible < 0) {
2067 			firstVisible -= linesScrolled;
2068 			lastVisible -= linesScrolled;
2069 
2070 			float scrollOffset;
2071 			if (firstVisible < -historySize) {
2072 				firstVisible = -historySize;
2073 				doScroll = true;
2074 				scrollOffset = -historySize * fFontHeight;
2075 				// We need to invalidate the lower linesScrolled lines of the
2076 				// visible text buffer, since those will be scrolled up and
2077 				// need to be replaced. We just use visibleDirty{Top,Bottom}
2078 				// for that purpose. Unless invoked from ScrollTo() (i.e.
2079 				// user-initiated scrolling) those are unused. In the unlikely
2080 				// case that the user is scrolling at the same time we may
2081 				// invalidate too many lines, since we have to extend the given
2082 				// region.
2083 				// Note that in the firstVisible == 0 case the new lines are
2084 				// already in the dirty region, so they will be updated anyway.
2085 				if (visibleDirtyTop <= visibleDirtyBottom) {
2086 					if (lastVisible < visibleDirtyTop)
2087 						visibleDirtyTop = lastVisible;
2088 					if (visibleDirtyBottom < lastVisible + linesScrolled)
2089 						visibleDirtyBottom = lastVisible + linesScrolled;
2090 				} else {
2091 					visibleDirtyTop = lastVisible + 1;
2092 					visibleDirtyBottom = lastVisible + linesScrolled;
2093 				}
2094 			} else
2095 				scrollOffset = fScrollOffset - linesScrolled * fFontHeight;
2096 
2097 			_ScrollTo(scrollOffset, false);
2098 		} else
2099 			doScroll = true;
2100 
2101 		if (doScroll && lastVisible >= firstVisible
2102 			&& !(info.IsDirtyRegionValid() && firstVisible >= info.dirtyTop
2103 				&& lastVisible <= info.dirtyBottom)) {
2104 			// scroll manually
2105 			float scrollBy = linesScrolled * fFontHeight;
2106 			BRect destRect(Frame().OffsetToCopy(B_ORIGIN));
2107 			BRect sourceRect(destRect.OffsetByCopy(0, scrollBy));
2108 
2109 			// invalidate the current cursor position before scrolling
2110 			_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2111 
2112 //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
2113 //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
2114 //destRect.left, destRect.top, destRect.right, destRect.bottom);
2115 			CopyBits(sourceRect, destRect);
2116 
2117 			fVisibleTextBuffer->ScrollBy(linesScrolled);
2118 		}
2119 
2120 		// move selection
2121 		if (fSelStart != fSelEnd) {
2122 			fSelStart.y -= linesScrolled;
2123 			fSelEnd.y -= linesScrolled;
2124 			fInitialSelectionStart.y -= linesScrolled;
2125 			fInitialSelectionEnd.y -= linesScrolled;
2126 
2127 			if (fSelStart.y < -historySize)
2128 				_Deselect();
2129 		}
2130 	}
2131 
2132 	// invalidate dirty region
2133 	if (info.IsDirtyRegionValid()) {
2134 		_InvalidateTextRect(0, info.dirtyTop, fTextBuffer->Width() - 1,
2135 			info.dirtyBottom);
2136 
2137 		// clear the selection, if affected
2138 		if (fSelStart != fSelEnd) {
2139 			// TODO: We're clearing the selection more often than necessary --
2140 			// to avoid that, we'd also need to track the x coordinates of the
2141 			// dirty range.
2142 			int32 selectionBottom = fSelEnd.x > 0 ? fSelEnd.y : fSelEnd.y - 1;
2143 			if (fSelStart.y <= info.dirtyBottom
2144 				&& info.dirtyTop <= selectionBottom) {
2145 				_Deselect();
2146 			}
2147 		}
2148 	}
2149 
2150 	if (visibleDirtyTop <= visibleDirtyBottom)
2151 		info.ExtendDirtyRegion(visibleDirtyTop, visibleDirtyBottom);
2152 
2153 	if (linesScrolled != 0 || info.IsDirtyRegionValid()) {
2154 		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, firstVisible,
2155 			info.dirtyTop, info.dirtyBottom);
2156 	}
2157 
2158 	// invalidate cursor, if it changed
2159 	TermPos cursor = fTextBuffer->Cursor();
2160 	if (fCursor != cursor || linesScrolled != 0) {
2161 		// Before we scrolled we did already invalidate the old cursor.
2162 		if (!doScroll)
2163 			_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2164 		fCursor = cursor;
2165 		_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2166 		_ActivateCursor(false);
2167 	}
2168 
2169 	info.Reset();
2170 }
2171 
2172 
2173 /*!	Write strings to PTY device. If encoding system isn't UTF8, change
2174 	encoding to UTF8 before writing PTY.
2175 */
2176 void
2177 TermView::_WritePTY(const char* text, int32 numBytes)
2178 {
2179 	if (fEncoding != M_UTF8) {
2180 		while (numBytes > 0) {
2181 			char buffer[1024];
2182 			int32 bufferSize = sizeof(buffer);
2183 			int32 sourceSize = numBytes;
2184 			int32 state = 0;
2185 			if (convert_to_utf8(fEncoding, text, &sourceSize, buffer,
2186 					&bufferSize, &state) != B_OK || bufferSize == 0) {
2187 				break;
2188 			}
2189 
2190 			fShell->Write(buffer, bufferSize);
2191 			text += sourceSize;
2192 			numBytes -= sourceSize;
2193 		}
2194 	} else {
2195 		fShell->Write(text, numBytes);
2196 	}
2197 }
2198 
2199 
2200 //! Returns the square of the actual pixel distance between both points
2201 float
2202 TermView::_MouseDistanceSinceLastClick(BPoint where)
2203 {
2204 	return (fLastClickPoint.x - where.x) * (fLastClickPoint.x - where.x)
2205 		+ (fLastClickPoint.y - where.y) * (fLastClickPoint.y - where.y);
2206 }
2207 
2208 
2209 void
2210 TermView::_SendMouseEvent(int32 buttons, int32 mode, int32 x, int32 y,
2211 	bool motion)
2212 {
2213 	char xtermButtons;
2214 	if (buttons == B_PRIMARY_MOUSE_BUTTON)
2215 		xtermButtons = 32 + 0;
2216  	else if (buttons == B_SECONDARY_MOUSE_BUTTON)
2217 		xtermButtons = 32 + 1;
2218 	else if (buttons == B_TERTIARY_MOUSE_BUTTON)
2219 		xtermButtons = 32 + 2;
2220 	else
2221 		xtermButtons = 32 + 3;
2222 
2223 	if (motion)
2224 		xtermButtons += 32;
2225 
2226 	char xtermX = x + 1 + 32;
2227 	char xtermY = y + 1 + 32;
2228 
2229 	char destBuffer[6];
2230 	destBuffer[0] = '\033';
2231 	destBuffer[1] = '[';
2232 	destBuffer[2] = 'M';
2233 	destBuffer[3] = xtermButtons;
2234 	destBuffer[4] = xtermX;
2235 	destBuffer[5] = xtermY;
2236 	fShell->Write(destBuffer, 6);
2237 }
2238 
2239 
2240 void
2241 TermView::MouseDown(BPoint where)
2242 {
2243 	if (!IsFocus())
2244 		MakeFocus();
2245 
2246 	int32 buttons;
2247 	int32 modifier;
2248 	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
2249 	Window()->CurrentMessage()->FindInt32("modifiers", &modifier);
2250 
2251 	fMouseButtons = buttons;
2252 
2253 	if (fReportAnyMouseEvent || fReportButtonMouseEvent
2254 		|| fReportNormalMouseEvent || fReportX10MouseEvent) {
2255   		TermPos clickPos = _ConvertToTerminal(where);
2256   		_SendMouseEvent(buttons, modifier, clickPos.x, clickPos.y, false);
2257 		return;
2258 	}
2259 
2260 	// paste button
2261 	if ((buttons & (B_SECONDARY_MOUSE_BUTTON | B_TERTIARY_MOUSE_BUTTON)) != 0) {
2262 		Paste(fMouseClipboard);
2263 		fLastClickPoint = where;
2264 		return;
2265 	}
2266 
2267 	// Select Region
2268 	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
2269 		int32 clicks;
2270 		Window()->CurrentMessage()->FindInt32("clicks", &clicks);
2271 
2272 		if (_HasSelection()) {
2273 			TermPos inPos = _ConvertToTerminal(where);
2274 			if (_CheckSelectedRegion(inPos)) {
2275 				if (modifier & B_CONTROL_KEY) {
2276 					BPoint p;
2277 					uint32 bt;
2278 					do {
2279 						GetMouse(&p, &bt);
2280 
2281 						if (bt == 0) {
2282 							_Deselect();
2283 							return;
2284 						}
2285 
2286 						snooze(40000);
2287 
2288 					} while (abs((int)(where.x - p.x)) < 4
2289 						&& abs((int)(where.y - p.y)) < 4);
2290 
2291 					InitiateDrag();
2292 					return;
2293 				}
2294 			}
2295 		}
2296 
2297 		// If mouse has moved too much, disable double/triple click.
2298 		if (_MouseDistanceSinceLastClick(where) > 8)
2299 			clicks = 1;
2300 
2301 		SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS,
2302 			B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS);
2303 
2304 		TermPos clickPos = _ConvertToTerminal(where);
2305 
2306 		if (modifier & B_SHIFT_KEY) {
2307 			fInitialSelectionStart = clickPos;
2308 			fInitialSelectionEnd = clickPos;
2309 			_ExtendSelection(fInitialSelectionStart, true, false);
2310 		} else {
2311 			_Deselect();
2312 			fInitialSelectionStart = clickPos;
2313 			fInitialSelectionEnd = clickPos;
2314 		}
2315 
2316 		// If clicks larger than 3, reset mouse click counter.
2317 		clicks = (clicks - 1) % 3 + 1;
2318 
2319 		switch (clicks) {
2320 			case 1:
2321 				fCheckMouseTracking = true;
2322 				fSelectGranularity = SELECT_CHARS;
2323 				break;
2324 
2325 			case 2:
2326 				_SelectWord(where, (modifier & B_SHIFT_KEY) != 0, false);
2327 				fMouseTracking = true;
2328 				fSelectGranularity = SELECT_WORDS;
2329 				break;
2330 
2331 			case 3:
2332 				_SelectLine(where, (modifier & B_SHIFT_KEY) != 0, false);
2333 				fMouseTracking = true;
2334 				fSelectGranularity = SELECT_LINES;
2335 				break;
2336 		}
2337 	}
2338 	fLastClickPoint = where;
2339 }
2340 
2341 
2342 void
2343 TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message)
2344 {
2345 	if (fReportAnyMouseEvent || fReportButtonMouseEvent) {
2346 		int32 modifier;
2347 		Window()->CurrentMessage()->FindInt32("modifiers", &modifier);
2348 
2349   		TermPos clickPos = _ConvertToTerminal(where);
2350 
2351   		if (fReportButtonMouseEvent) {
2352   			if (fPrevPos.x != clickPos.x || fPrevPos.y != clickPos.y) {
2353 		  		_SendMouseEvent(fMouseButtons, modifier, clickPos.x, clickPos.y,
2354 					true);
2355   			}
2356   			fPrevPos = clickPos;
2357   			return;
2358   		}
2359   		_SendMouseEvent(fMouseButtons, modifier, clickPos.x, clickPos.y, true);
2360 		return;
2361 	}
2362 
2363 	if (fCheckMouseTracking) {
2364 		if (_MouseDistanceSinceLastClick(where) > 9)
2365 			fMouseTracking = true;
2366 	}
2367 	if (!fMouseTracking)
2368 		return;
2369 
2370 	bool doAutoScroll = false;
2371 
2372 	if (where.y < 0) {
2373 		doAutoScroll = true;
2374 		fAutoScrollSpeed = where.y;
2375 		where.x = 0;
2376 		where.y = 0;
2377 	}
2378 
2379 	BRect bounds(Bounds());
2380 	if (where.y > bounds.bottom) {
2381 		doAutoScroll = true;
2382 		fAutoScrollSpeed = where.y - bounds.bottom;
2383 		where.x = bounds.right;
2384 		where.y = bounds.bottom;
2385 	}
2386 
2387 	if (doAutoScroll) {
2388 		if (fAutoScrollRunner == NULL) {
2389 			BMessage message(kAutoScroll);
2390 			fAutoScrollRunner = new (std::nothrow) BMessageRunner(
2391 				BMessenger(this), &message, 10000);
2392 		}
2393 	} else {
2394 		delete fAutoScrollRunner;
2395 		fAutoScrollRunner = NULL;
2396 	}
2397 
2398 	switch (fSelectGranularity) {
2399 		case SELECT_CHARS:
2400 		{
2401 			// If we just start selecting, we first select the initially
2402 			// hit char, so that we get a proper initial selection -- the char
2403 			// in question, which will thus always be selected, regardless of
2404 			// whether selecting forward or backward.
2405 			if (fInitialSelectionStart == fInitialSelectionEnd) {
2406 				_Select(fInitialSelectionStart, fInitialSelectionEnd, true,
2407 					true);
2408 			}
2409 
2410 			_ExtendSelection(_ConvertToTerminal(where), true, true);
2411 			break;
2412 		}
2413 		case SELECT_WORDS:
2414 			_SelectWord(where, true, true);
2415 			break;
2416 		case SELECT_LINES:
2417 			_SelectLine(where, true, true);
2418 			break;
2419 	}
2420 }
2421 
2422 
2423 void
2424 TermView::MouseUp(BPoint where)
2425 {
2426 	fCheckMouseTracking = false;
2427 	fMouseTracking = false;
2428 
2429 	if (fAutoScrollRunner != NULL) {
2430 		delete fAutoScrollRunner;
2431 		fAutoScrollRunner = NULL;
2432 	}
2433 
2434 	// When releasing the first mouse button, we copy the selected text to the
2435 	// clipboard.
2436 	int32 buttons;
2437 	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
2438 
2439 	if (fReportAnyMouseEvent || fReportButtonMouseEvent
2440 		|| fReportNormalMouseEvent) {
2441 	  	TermPos clickPos = _ConvertToTerminal(where);
2442 	  	_SendMouseEvent(0, 0, clickPos.x, clickPos.y, false);
2443 	} else {
2444 		if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0
2445 			&& (fMouseButtons & B_PRIMARY_MOUSE_BUTTON) != 0) {
2446 			Copy(fMouseClipboard);
2447 		}
2448 
2449 	}
2450 	fMouseButtons = buttons;
2451 }
2452 
2453 
2454 //! Select a range of text.
2455 void
2456 TermView::_Select(TermPos start, TermPos end, bool inclusive,
2457 	bool setInitialSelection)
2458 {
2459 	BAutolock _(fTextBuffer);
2460 
2461 	_SynchronizeWithTextBuffer(0, -1);
2462 
2463 	if (end < start)
2464 		std::swap(start, end);
2465 
2466 	if (inclusive)
2467 		end.x++;
2468 
2469 //debug_printf("TermView::_Select(): (%ld, %ld) - (%ld, %ld)\n", start.x,
2470 //start.y, end.x, end.y);
2471 
2472 	if (start.x < 0)
2473 		start.x = 0;
2474 	if (end.x >= fColumns)
2475 		end.x = fColumns;
2476 
2477 	TermPos minPos(0, -fTextBuffer->HistorySize());
2478 	TermPos maxPos(0, fTextBuffer->Height());
2479 	start = restrict_value(start, minPos, maxPos);
2480 	end = restrict_value(end, minPos, maxPos);
2481 
2482 	// if the end is past the end of the line, select the line break, too
2483 	if (fTextBuffer->LineLength(end.y) < end.x
2484 			&& end.y < fTextBuffer->Height()) {
2485 		end.y++;
2486 		end.x = 0;
2487 	}
2488 
2489 	if (fTextBuffer->IsFullWidthChar(start.y, start.x)) {
2490 		start.x--;
2491 		if (start.x < 0)
2492 			start.x = 0;
2493 	}
2494 
2495 	if (fTextBuffer->IsFullWidthChar(end.y, end.x)) {
2496 		end.x++;
2497 		if (end.x >= fColumns)
2498 			end.x = fColumns;
2499 	}
2500 
2501 	if (fSelStart != fSelEnd)
2502 		_InvalidateTextRange(fSelStart, fSelEnd);
2503 
2504 	fSelStart = start;
2505 	fSelEnd = end;
2506 
2507 	if (setInitialSelection) {
2508 		fInitialSelectionStart = fSelStart;
2509 		fInitialSelectionEnd = fSelEnd;
2510 	}
2511 
2512 	_InvalidateTextRange(fSelStart, fSelEnd);
2513 }
2514 
2515 
2516 //! Extend selection (shift + mouse click).
2517 void
2518 TermView::_ExtendSelection(TermPos pos, bool inclusive,
2519 	bool useInitialSelection)
2520 {
2521 	if (!useInitialSelection && !_HasSelection())
2522 		return;
2523 
2524 	TermPos start = fSelStart;
2525 	TermPos end = fSelEnd;
2526 
2527 	if (useInitialSelection) {
2528 		start = fInitialSelectionStart;
2529 		end = fInitialSelectionEnd;
2530 	}
2531 
2532 	if (inclusive) {
2533 		if (pos >= start && pos >= end)
2534 			pos.x++;
2535 	}
2536 
2537 	if (pos < start)
2538 		_Select(pos, end, false, !useInitialSelection);
2539 	else if (pos > end)
2540 		_Select(start, pos, false, !useInitialSelection);
2541 	else if (useInitialSelection)
2542 		_Select(start, end, false, false);
2543 }
2544 
2545 
2546 // clear the selection.
2547 void
2548 TermView::_Deselect()
2549 {
2550 //debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection());
2551 	if (!_HasSelection())
2552 		return;
2553 
2554 	_InvalidateTextRange(fSelStart, fSelEnd);
2555 
2556 	fSelStart.SetTo(0, 0);
2557 	fSelEnd.SetTo(0, 0);
2558 	fInitialSelectionStart.SetTo(0, 0);
2559 	fInitialSelectionEnd.SetTo(0, 0);
2560 }
2561 
2562 
2563 bool
2564 TermView::_HasSelection() const
2565 {
2566 	return fSelStart != fSelEnd;
2567 }
2568 
2569 
2570 void
2571 TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection)
2572 {
2573 	BAutolock _(fTextBuffer);
2574 
2575 	TermPos pos = _ConvertToTerminal(where);
2576 	TermPos start, end;
2577 	if (!fTextBuffer->FindWord(pos, fCharClassifier, true, start, end))
2578 		return;
2579 
2580 	if (extend) {
2581 		if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart))
2582 			_ExtendSelection(start, false, useInitialSelection);
2583 		else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd))
2584 			_ExtendSelection(end, false, useInitialSelection);
2585 		else if (useInitialSelection)
2586 			_Select(start, end, false, false);
2587 	} else
2588 		_Select(start, end, false, !useInitialSelection);
2589 }
2590 
2591 
2592 void
2593 TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection)
2594 {
2595 	TermPos start = TermPos(0, _ConvertToTerminal(where).y);
2596 	TermPos end = TermPos(0, start.y + 1);
2597 
2598 	if (extend) {
2599 		if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart))
2600 			_ExtendSelection(start, false, useInitialSelection);
2601 		else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd))
2602 			_ExtendSelection(end, false, useInitialSelection);
2603 		else if (useInitialSelection)
2604 			_Select(start, end, false, false);
2605 	} else
2606 		_Select(start, end, false, !useInitialSelection);
2607 }
2608 
2609 
2610 void
2611 TermView::_AutoScrollUpdate()
2612 {
2613 	if (fMouseTracking && fAutoScrollRunner != NULL && fScrollBar != NULL) {
2614 		float value = fScrollBar->Value();
2615 		_ScrollTo(value + fAutoScrollSpeed, true);
2616 		if (fAutoScrollSpeed < 0) {
2617 			_ExtendSelection(_ConvertToTerminal(BPoint(0, 0)), true, true);
2618 		} else {
2619 			_ExtendSelection(_ConvertToTerminal(Bounds().RightBottom()), true,
2620 				true);
2621 		}
2622 	}
2623 }
2624 
2625 
2626 bool
2627 TermView::_CheckSelectedRegion(const TermPos &pos) const
2628 {
2629 	return pos >= fSelStart && pos < fSelEnd;
2630 }
2631 
2632 
2633 bool
2634 TermView::_CheckSelectedRegion(int32 row, int32 firstColumn,
2635 	int32& lastColumn) const
2636 {
2637 	if (fSelStart == fSelEnd)
2638 		return false;
2639 
2640 	if (row == fSelStart.y && firstColumn < fSelStart.x
2641 			&& lastColumn >= fSelStart.x) {
2642 		// region starts before the selection, but intersects with it
2643 		lastColumn = fSelStart.x - 1;
2644 		return false;
2645 	}
2646 
2647 	if (row == fSelEnd.y && firstColumn < fSelEnd.x
2648 			&& lastColumn >= fSelEnd.x) {
2649 		// region starts in the selection, but exceeds the end
2650 		lastColumn = fSelEnd.x - 1;
2651 		return true;
2652 	}
2653 
2654 	TermPos pos(firstColumn, row);
2655 	return pos >= fSelStart && pos < fSelEnd;
2656 }
2657 
2658 
2659 void
2660 TermView::GetFrameSize(float *width, float *height)
2661 {
2662 	int32 historySize;
2663 	{
2664 		BAutolock _(fTextBuffer);
2665 		historySize = fTextBuffer->HistorySize();
2666 	}
2667 
2668 	if (width != NULL)
2669 		*width = fColumns * fFontWidth;
2670 
2671 	if (height != NULL)
2672 		*height = (fRows + historySize) * fFontHeight;
2673 }
2674 
2675 
2676 // Find a string, and select it if found
2677 bool
2678 TermView::Find(const BString &str, bool forwardSearch, bool matchCase,
2679 	bool matchWord)
2680 {
2681 	BAutolock _(fTextBuffer);
2682 	_SynchronizeWithTextBuffer(0, -1);
2683 
2684 	TermPos start;
2685 	if (_HasSelection()) {
2686 		if (forwardSearch)
2687 			start = fSelEnd;
2688 		else
2689 			start = fSelStart;
2690 	} else {
2691 		// search from the very beginning/end
2692 		if (forwardSearch)
2693 			start = TermPos(0, -fTextBuffer->HistorySize());
2694 		else
2695 			start = TermPos(0, fTextBuffer->Height());
2696 	}
2697 
2698 	TermPos matchStart, matchEnd;
2699 	if (!fTextBuffer->Find(str.String(), start, forwardSearch, matchCase,
2700 			matchWord, matchStart, matchEnd)) {
2701 		return false;
2702 	}
2703 
2704 	_Select(matchStart, matchEnd, false, true);
2705 	_ScrollToRange(fSelStart, fSelEnd);
2706 
2707 	return true;
2708 }
2709 
2710 
2711 //! Get the selected text and copy to str
2712 void
2713 TermView::GetSelection(BString &str)
2714 {
2715 	str.SetTo("");
2716 	BAutolock _(fTextBuffer);
2717 	fTextBuffer->GetStringFromRegion(str, fSelStart, fSelEnd);
2718 }
2719 
2720 
2721 void
2722 TermView::NotifyQuit(int32 reason)
2723 {
2724 	// implemented in subclasses
2725 }
2726 
2727 
2728 void
2729 TermView::CheckShellGone()
2730 {
2731 	if (!fShell)
2732 		return;
2733 
2734 	// check, if the shell does still live
2735 	pid_t pid = fShell->ProcessID();
2736 	team_info info;
2737 	if (get_team_info(pid, &info) == B_BAD_TEAM_ID) {
2738 		// the shell is gone
2739 		NotifyQuit(0);
2740 	}
2741 }
2742 
2743 
2744 void
2745 TermView::InitiateDrag()
2746 {
2747 	BAutolock _(fTextBuffer);
2748 
2749 	BString copyStr("");
2750 	fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd);
2751 
2752 	BMessage message(B_MIME_DATA);
2753 	message.AddData("text/plain", B_MIME_TYPE, copyStr.String(),
2754 		copyStr.Length());
2755 
2756 	BPoint start = _ConvertFromTerminal(fSelStart);
2757 	BPoint end = _ConvertFromTerminal(fSelEnd);
2758 
2759 	BRect rect;
2760 	if (fSelStart.y == fSelEnd.y)
2761 		rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight);
2762 	else
2763 		rect.Set(0, start.y, fColumns * fFontWidth, end.y + fFontHeight);
2764 
2765 	rect = rect & Bounds();
2766 
2767 	DragMessage(&message, rect);
2768 }
2769 
2770 
2771 /* static */
2772 void
2773 TermView::AboutRequested()
2774 {
2775 	BAlert *alert = new (std::nothrow) BAlert("about",
2776 		B_TRANSLATE("Terminal\n\n"
2777 			"written by Kazuho Okui and Takashi Murai\n"
2778 			"updated by Kian Duffy and others\n\n"
2779 			"Copyright " B_UTF8_COPYRIGHT "2003-2009, Haiku.\n"),
2780 		B_TRANSLATE("OK"));
2781 	if (alert != NULL)
2782 		alert->Go();
2783 }
2784 
2785 
2786 void
2787 TermView::_ScrollTo(float y, bool scrollGfx)
2788 {
2789 	if (!scrollGfx)
2790 		fScrollOffset = y;
2791 
2792 	if (fScrollBar != NULL)
2793 		fScrollBar->SetValue(y);
2794 	else
2795 		ScrollTo(BPoint(0, y));
2796 }
2797 
2798 
2799 void
2800 TermView::_ScrollToRange(TermPos start, TermPos end)
2801 {
2802 	if (start > end)
2803 		std::swap(start, end);
2804 
2805 	float startY = _LineOffset(start.y);
2806 	float endY = _LineOffset(end.y) + fFontHeight - 1;
2807 	float height = Bounds().Height();
2808 
2809 	if (endY - startY > height) {
2810 		// The range is greater than the height. Scroll to the closest border.
2811 
2812 		// already as good as it gets?
2813 		if (startY <= 0 && endY >= height)
2814 			return;
2815 
2816 		if (startY > 0) {
2817 			// scroll down to align the start with the top of the view
2818 			_ScrollTo(fScrollOffset + startY, true);
2819 		} else {
2820 			// scroll up to align the end with the bottom of the view
2821 			_ScrollTo(fScrollOffset + endY - height, true);
2822 		}
2823 	} else {
2824 		// The range is smaller than the height.
2825 
2826 		// already visible?
2827 		if (startY >= 0 && endY <= height)
2828 			return;
2829 
2830 		if (startY < 0) {
2831 			// scroll up to make the start visible
2832 			_ScrollTo(fScrollOffset + startY, true);
2833 		} else {
2834 			// scroll down to make the end visible
2835 			_ScrollTo(fScrollOffset + endY - height, true);
2836 		}
2837 	}
2838 }
2839 
2840 
2841 void
2842 TermView::DisableResizeView(int32 disableCount)
2843 {
2844 	fResizeViewDisableCount += disableCount;
2845 }
2846 
2847 
2848 void
2849 TermView::_DrawInlineMethodString()
2850 {
2851 	if (!fInline || !fInline->String())
2852 		return;
2853 
2854 	const int32 numChars = BString(fInline->String()).CountChars();
2855 
2856 	BPoint startPoint = _ConvertFromTerminal(fCursor);
2857 	BPoint endPoint = startPoint;
2858 	endPoint.x += fFontWidth * numChars;
2859 	endPoint.y += fFontHeight + 1;
2860 
2861 	BRect eraseRect(startPoint, endPoint);
2862 
2863 	PushState();
2864 	SetHighColor(kTermColorTable[7]);
2865 	FillRect(eraseRect);
2866 	PopState();
2867 
2868 	BPoint loc = _ConvertFromTerminal(fCursor);
2869 	loc.y += fFontHeight;
2870 	SetFont(&fHalfFont);
2871 	SetHighColor(kTermColorTable[0]);
2872 	SetLowColor(kTermColorTable[7]);
2873 	DrawString(fInline->String(), loc);
2874 }
2875 
2876 
2877 void
2878 TermView::_HandleInputMethodChanged(BMessage *message)
2879 {
2880 	const char *string = NULL;
2881 	if (message->FindString("be:string", &string) < B_OK || string == NULL)
2882 		return;
2883 
2884 	_ActivateCursor(false);
2885 
2886 	if (IsFocus())
2887 		be_app->ObscureCursor();
2888 
2889 	// If we find the "be:confirmed" boolean (and the boolean is true),
2890 	// it means it's over for now, so the current InlineInput object
2891 	// should become inactive. We will probably receive a
2892 	// B_INPUT_METHOD_STOPPED message after this one.
2893 	bool confirmed;
2894 	if (message->FindBool("be:confirmed", &confirmed) != B_OK)
2895 		confirmed = false;
2896 
2897 	fInline->SetString("");
2898 
2899 	Invalidate();
2900 	// TODO: Debug only
2901 	snooze(100000);
2902 
2903 	fInline->SetString(string);
2904 	fInline->ResetClauses();
2905 
2906 	if (!confirmed && !fInline->IsActive())
2907 		fInline->SetActive(true);
2908 
2909 	// Get the clauses, and pass them to the InlineInput object
2910 	// TODO: Find out if what we did it's ok, currently we don't consider
2911 	// clauses at all, while the bebook says we should; though the visual
2912 	// effect we obtained seems correct. Weird.
2913 	int32 clauseCount = 0;
2914 	int32 clauseStart;
2915 	int32 clauseEnd;
2916 	while (message->FindInt32("be:clause_start", clauseCount, &clauseStart)
2917 			== B_OK
2918 		&& message->FindInt32("be:clause_end", clauseCount, &clauseEnd)
2919 			== B_OK) {
2920 		if (!fInline->AddClause(clauseStart, clauseEnd))
2921 			break;
2922 		clauseCount++;
2923 	}
2924 
2925 	if (confirmed) {
2926 		fInline->SetString("");
2927 		_ActivateCursor(true);
2928 
2929 		// now we need to feed ourselves the individual characters as if the
2930 		// user would have pressed them now - this lets KeyDown() pick out all
2931 		// the special characters like B_BACKSPACE, cursor keys and the like:
2932 		const char* currPos = string;
2933 		const char* prevPos = currPos;
2934 		while (*currPos != '\0') {
2935 			if ((*currPos & 0xC0) == 0xC0) {
2936 				// found the start of an UTF-8 char, we collect while it lasts
2937 				++currPos;
2938 				while ((*currPos & 0xC0) == 0x80)
2939 					++currPos;
2940 			} else if ((*currPos & 0xC0) == 0x80) {
2941 				// illegal: character starts with utf-8 intermediate byte, skip it
2942 				prevPos = ++currPos;
2943 			} else {
2944 				// single byte character/code, just feed that
2945 				++currPos;
2946 			}
2947 			KeyDown(prevPos, currPos - prevPos);
2948 			prevPos = currPos;
2949 		}
2950 
2951 		Invalidate();
2952 	} else {
2953 		// temporarily show transient state of inline input
2954 		int32 selectionStart = 0;
2955 		int32 selectionEnd = 0;
2956 		message->FindInt32("be:selection", 0, &selectionStart);
2957 		message->FindInt32("be:selection", 1, &selectionEnd);
2958 
2959 		fInline->SetSelectionOffset(selectionStart);
2960 		fInline->SetSelectionLength(selectionEnd - selectionStart);
2961 		Invalidate();
2962 	}
2963 }
2964 
2965 
2966 void
2967 TermView::_HandleInputMethodLocationRequest()
2968 {
2969 	BMessage message(B_INPUT_METHOD_EVENT);
2970 	message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
2971 
2972 	BString string(fInline->String());
2973 
2974 	const int32 &limit = string.CountChars();
2975 	BPoint where = _ConvertFromTerminal(fCursor);
2976 	where.y += fFontHeight;
2977 
2978 	for (int32 i = 0; i < limit; i++) {
2979 		// Add the location of the UTF8 characters
2980 
2981 		where.x += fFontWidth;
2982 		ConvertToScreen(&where);
2983 
2984 		message.AddPoint("be:location_reply", where);
2985 		message.AddFloat("be:height_reply", fFontHeight);
2986 	}
2987 
2988 	fInline->Method()->SendMessage(&message);
2989 }
2990 
2991 
2992 
2993 void
2994 TermView::_CancelInputMethod()
2995 {
2996 	if (!fInline)
2997 		return;
2998 
2999 	InlineInput *inlineInput = fInline;
3000 	fInline = NULL;
3001 
3002 	if (inlineInput->IsActive() && Window()) {
3003 		Invalidate();
3004 
3005 		BMessage message(B_INPUT_METHOD_EVENT);
3006 		message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
3007 		inlineInput->Method()->SendMessage(&message);
3008 	}
3009 
3010 	delete inlineInput;
3011 }
3012 
3013