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