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