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