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