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