TermView.cpp revision 552580cd
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
52extern int function_keycode_table[];
53extern char *function_key_char_table[];
54
55const 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
70enum {
71	SELECT_CHARS,
72	SELECT_WORDS,
73	SELECT_LINES
74};
75
76
77static 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
94static const uint32 kUpdateSigWinch = 'Rwin';
95static const uint32 kBlinkCursor = 'BlCr';
96static const uint32 kAutoScroll = 'AScr';
97
98static const bigtime_t kSyncUpdateGranularity = 100000;	// 0.1 s
99
100static const int32 kCursorBlinkIntervals = 3;
101static const int32 kCursorVisibleIntervals = 2;
102static const bigtime_t kCursorBlinkInterval = 500000;
103
104static const rgb_color kBlackColor = { 0, 0, 0, 255 };
105static const rgb_color kWhiteColor = { 255, 255, 255, 255 };
106
107static const char* kDefaultSpecialWordChars = ":@-./_~";
108
109
110template<typename Type>
111static inline Type
112restrict_value(const Type& value, const Type& min, const Type& max)
113{
114	return value < min ? min : (value > max ? max : value);
115}
116
117
118class TermView::CharClassifier : public TerminalCharClassifier
119{
120public:
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
142private:
143	const char*	fSpecialWordChars;
144};
145
146
147inline int32
148TermView::_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
160inline float
161TermView::_LineOffset(int32 index)
162{
163	return index * fFontHeight - fScrollOffset;
164}
165
166
167// convert view coordinates to terminal text buffer position
168inline TermPos
169TermView::_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
176inline BPoint
177TermView::_ConvertFromTerminal(const TermPos &pos)
178{
179	return BPoint(fFontWidth * pos.x, _LineOffset(pos.y));
180}
181
182
183inline void
184TermView::_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
194TermView::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
237TermView::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
292TermView::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
356status_t
357TermView::_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
409TermView::~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 */
426BArchivable *
427TermView::Instantiate(BMessage* data)
428{
429	if (validate_instantiation(data, "TermView"))
430		return new (std::nothrow) TermView(data);
431
432	return NULL;
433}
434
435
436status_t
437TermView::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
456void
457TermView::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
466const char *
467TermView::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
477void
478TermView::GetFontSize(int* _width, int* _height)
479{
480	*_width = fFontWidth;
481	*_height = fFontHeight;
482}
483
484
485//! Set number of rows and columns in terminal
486BRect
487TermView::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
538void
539TermView::SetTextColor(rgb_color fore, rgb_color back)
540{
541	fTextForeColor = fore;
542	fTextBackColor = back;
543
544	SetLowColor(fTextBackColor);
545}
546
547
548void
549TermView::SetSelectColor(rgb_color fore, rgb_color back)
550{
551	fSelectForeColor = fore;
552	fSelectBackColor = back;
553}
554
555
556void
557TermView::SetCursorColor(rgb_color fore, rgb_color back)
558{
559	fCursorForeColor = fore;
560	fCursorBackColor = back;
561}
562
563
564int
565TermView::Encoding() const
566{
567	return fEncoding;
568}
569
570
571void
572TermView::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
584void
585TermView::GetTermFont(BFont *font) const
586{
587	if (font != NULL)
588		*font = fHalfFont;
589}
590
591
592//! Sets font for terminal
593void
594TermView::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
635void
636TermView::SetScrollBar(BScrollBar *scrollBar)
637{
638	fScrollBar = scrollBar;
639	if (fScrollBar != NULL) {
640		fScrollBar->SetSteps(fFontHeight, fFontHeight * fTermRows);
641	}
642}
643
644
645void
646TermView::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
655void
656TermView::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
680void
681TermView::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
699void
700TermView::SelectAll()
701{
702	BAutolock _(fTextBuffer);
703
704	_Select(TermPos(0, -fTextBuffer->HistorySize()),
705		TermPos(0, fTextBuffer->Height()), false, true);
706}
707
708
709void
710TermView::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
732void
733TermView::_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
751status_t
752TermView::_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
764void
765TermView::_DetachShell()
766{
767	fShell->ViewDetached();
768	fShell = NULL;
769}
770
771
772//! Draw part of a line in the given view.
773void
774TermView::_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*/
842void
843TermView::_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
882bool
883TermView::_IsCursorVisible() const
884{
885	return fCursorState < kCursorVisibleIntervals;
886}
887
888
889void
890TermView::_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
905void
906TermView::_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.
917void
918TermView::_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
942void
943TermView::_UpdateSIGWINCH()
944{
945	if (fFrameResized) {
946		fShell->UpdateWindowSize(fTermRows, fTermColumns);
947		fFrameResized = false;
948	}
949}
950
951
952void
953TermView::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
977void
978TermView::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
993void
994TermView::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
1071void
1072TermView::_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
1117void
1118TermView::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
1140void
1141TermView::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	int32 toWriteLen = -1;
1176
1177	switch (*bytes) {
1178		case B_RETURN:
1179			if (rawChar == B_RETURN)
1180				toWrite = "\r";
1181			break;
1182
1183		case B_DELETE:
1184			toWrite = DELETE_KEY_CODE;
1185			break;
1186
1187		case B_BACKSPACE:
1188			// Translate only the actual backspace key to the backspace
1189			// code. CTRL-H shall just be echoed.
1190			if (!((mod & B_CONTROL_KEY) && rawChar == 'h'))
1191				toWrite = BACKSPACE_KEY_CODE;
1192			break;
1193
1194		case B_LEFT_ARROW:
1195			if (rawChar == B_LEFT_ARROW) {
1196				if (mod & B_SHIFT_KEY) {
1197					BMessage message(MSG_PREVIOUS_TAB);
1198					message.AddPointer("termView", this);
1199					Window()->PostMessage(&message);
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				} else if ((mod & B_CONTROL_KEY) || (mod & B_COMMAND_KEY)) {
1214					toWrite = CTRL_RIGHT_ARROW_KEY_CODE;
1215				} else
1216					toWrite = RIGHT_ARROW_KEY_CODE;
1217			}
1218			break;
1219
1220		case B_UP_ARROW:
1221			if (mod & B_SHIFT_KEY) {
1222				_ScrollTo(fScrollOffset - fFontHeight, true);
1223				return;
1224			}
1225			if (rawChar == B_UP_ARROW) {
1226				if (mod & B_CONTROL_KEY)
1227					toWrite = CTRL_UP_ARROW_KEY_CODE;
1228				else
1229					toWrite = UP_ARROW_KEY_CODE;
1230			}
1231			break;
1232
1233		case B_DOWN_ARROW:
1234			if (mod & B_SHIFT_KEY) {
1235				_ScrollTo(fScrollOffset + fFontHeight, true);
1236				return;
1237			}
1238
1239			if (rawChar == B_DOWN_ARROW) {
1240				if (mod & B_CONTROL_KEY)
1241					toWrite = CTRL_DOWN_ARROW_KEY_CODE;
1242				else
1243					toWrite = DOWN_ARROW_KEY_CODE;
1244			}
1245			break;
1246
1247		case B_INSERT:
1248			if (rawChar == B_INSERT)
1249				toWrite = INSERT_KEY_CODE;
1250			break;
1251
1252		case B_HOME:
1253			if (rawChar == B_HOME)
1254				toWrite = HOME_KEY_CODE;
1255			break;
1256
1257		case B_END:
1258			if (rawChar == B_END)
1259				toWrite = END_KEY_CODE;
1260			break;
1261
1262		case B_PAGE_UP:
1263			if (mod & B_SHIFT_KEY) {
1264				_ScrollTo(fScrollOffset - fFontHeight  * fTermRows, true);
1265				return;
1266			}
1267			if (rawChar == B_PAGE_UP)
1268				toWrite = PAGE_UP_KEY_CODE;
1269			break;
1270
1271		case B_PAGE_DOWN:
1272			if (mod & B_SHIFT_KEY) {
1273				_ScrollTo(fScrollOffset + fFontHeight * fTermRows, true);
1274				return;
1275			}
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					fShell->Write(function_key_char_table[i], 5);
1285					return;
1286				}
1287			}
1288			break;
1289		default:
1290			toWrite = bytes;
1291			toWriteLen = 1;
1292			break;
1293	}
1294
1295	if (toWrite) {
1296		_ScrollTo(0, true);
1297		fShell->Write(toWrite, toWriteLen < 0 ? strlen(toWrite) : toWriteLen);
1298	}
1299}
1300
1301
1302void
1303TermView::FrameResized(float width, float height)
1304{
1305//debug_printf("TermView::FrameResized(%f, %f)\n", width, height);
1306	int32 columns = ((int32)width + 1) / fFontWidth;
1307	int32 rows = ((int32)height + 1) / fFontHeight;
1308
1309	if (columns == fTermColumns && rows == fTermRows)
1310		return;
1311
1312	SetTermSize(rows, columns, false);
1313
1314	fFrameResized = true;
1315}
1316
1317
1318void
1319TermView::MessageReceived(BMessage *msg)
1320{
1321	entry_ref ref;
1322	char *ctrl_l = "\x0c";
1323
1324	// first check for any dropped message
1325	if (msg->WasDropped()) {
1326		char *text;
1327		int32 numBytes;
1328		//rgb_color *color;
1329
1330		int32 i = 0;
1331
1332		if (msg->FindRef("refs", i++, &ref) == B_OK) {
1333			_DoFileDrop(ref);
1334
1335			while (msg->FindRef("refs", i++, &ref) == B_OK) {
1336				_WritePTY(" ", 1);
1337				_DoFileDrop(ref);
1338			}
1339			return;
1340#if 0
1341		} else if (msg->FindData("RGBColor", B_RGB_COLOR_TYPE,
1342				(const void **)&color, &numBytes) == B_OK
1343				 && numBytes == sizeof(color)) {
1344			// TODO: handle color drop
1345			// maybe only on replicants ?
1346			return;
1347#endif
1348		} else if (msg->FindData("text/plain", B_MIME_TYPE,
1349			 	(const void **)&text, &numBytes) == B_OK) {
1350			_WritePTY(text, numBytes);
1351			return;
1352		}
1353	}
1354
1355	switch (msg->what){
1356		case B_ABOUT_REQUESTED:
1357			// (replicant) about box requested
1358			_AboutRequested();
1359			break;
1360
1361		case B_SIMPLE_DATA:
1362		case B_REFS_RECEIVED:
1363		{
1364			// handle refs if they weren't dropped
1365			int32 i = 0;
1366			if (msg->FindRef("refs", i++, &ref) == B_OK) {
1367				_DoFileDrop(ref);
1368
1369				while (msg->FindRef("refs", i++, &ref) == B_OK) {
1370					_WritePTY(" ", 1);
1371					_DoFileDrop(ref);
1372				}
1373			} else
1374				BView::MessageReceived(msg);
1375			break;
1376		}
1377
1378		case B_COPY:
1379			Copy(be_clipboard);
1380			break;
1381
1382		case B_PASTE:
1383		{
1384			int32 code;
1385			if (msg->FindInt32("index", &code) == B_OK)
1386				Paste(be_clipboard);
1387			break;
1388		}
1389
1390		case B_SELECT_ALL:
1391			SelectAll();
1392			break;
1393
1394		case B_SET_PROPERTY:
1395		{
1396			int32 i;
1397			int32 encodingID;
1398			BMessage specifier;
1399			msg->GetCurrentSpecifier(&i, &specifier);
1400			if (!strcmp("encoding", specifier.FindString("property", i))){
1401				msg->FindInt32 ("data", &encodingID);
1402				SetEncoding(encodingID);
1403				msg->SendReply(B_REPLY);
1404			} else {
1405				BView::MessageReceived(msg);
1406			}
1407			break;
1408		}
1409
1410		case B_GET_PROPERTY:
1411		{
1412			int32 i;
1413			BMessage specifier;
1414			msg->GetCurrentSpecifier(&i, &specifier);
1415			if (!strcmp("encoding", specifier.FindString("property", i))){
1416				BMessage reply(B_REPLY);
1417				reply.AddInt32("result", Encoding());
1418				msg->SendReply(&reply);
1419			} else if (!strcmp("tty", specifier.FindString("property", i))) {
1420				BMessage reply(B_REPLY);
1421				reply.AddString("result", TerminalName());
1422				msg->SendReply(&reply);
1423			} else {
1424				BView::MessageReceived(msg);
1425			}
1426			break;
1427		}
1428
1429		case MENU_CLEAR_ALL:
1430			Clear();
1431			fShell->Write(ctrl_l, 1);
1432			break;
1433
1434
1435//  case B_INPUT_METHOD_EVENT:
1436//    {
1437   //   int32 op;
1438  //    msg->FindInt32("be:opcode", &op);
1439   //   switch (op){
1440   //   case B_INPUT_METHOD_STARTED:
1441	//DoIMStart(msg);
1442//	break;
1443
1444//      case B_INPUT_METHOD_STOPPED:
1445//	DoIMStop(msg);
1446//	break;
1447
1448//      case B_INPUT_METHOD_CHANGED:
1449//	DoIMChange(msg);
1450//	break;
1451
1452//      case B_INPUT_METHOD_LOCATION_REQUEST:
1453//	DoIMLocation(msg);
1454//	break;
1455    //  }
1456   // }
1457		case kBlinkCursor:
1458			_BlinkCursor();
1459			break;
1460		case kUpdateSigWinch:
1461			_UpdateSIGWINCH();
1462			break;
1463		case kAutoScroll:
1464			_AutoScrollUpdate();
1465			break;
1466		case MSG_TERMINAL_BUFFER_CHANGED:
1467		{
1468			BAutolock _(fTextBuffer);
1469			_SynchronizeWithTextBuffer(0, -1);
1470			break;
1471		}
1472		case MSG_SET_TERMNAL_TITLE:
1473		{
1474			const char* title;
1475			if (msg->FindString("title", &title) == B_OK)
1476				SetTitle(title);
1477			break;
1478		}
1479		case MSG_QUIT_TERMNAL:
1480		{
1481			int32 reason;
1482			if (msg->FindInt32("reason", &reason) != B_OK)
1483				reason = 0;
1484			NotifyQuit(reason);
1485			break;
1486		}
1487		default:
1488			BView::MessageReceived(msg);
1489			break;
1490	}
1491}
1492
1493
1494status_t
1495TermView::GetSupportedSuites(BMessage *message)
1496{
1497	BPropertyInfo propInfo(sPropList);
1498	message->AddString("suites", "suite/vnd.naan-termview");
1499	message->AddFlat("messages", &propInfo);
1500	return BView::GetSupportedSuites(message);
1501}
1502
1503
1504void
1505TermView::ScrollTo(BPoint where)
1506{
1507//debug_printf("TermView::ScrollTo(): %f -> %f\n", fScrollOffset, where.y);
1508	float diff = where.y - fScrollOffset;
1509	if (diff == 0)
1510		return;
1511
1512	float bottom = Bounds().bottom;
1513	int32 oldFirstLine = _LineAt(0);
1514	int32 oldLastLine = _LineAt(bottom);
1515	int32 newFirstLine = _LineAt(diff);
1516	int32 newLastLine = _LineAt(bottom + diff);
1517
1518	fScrollOffset = where.y;
1519
1520	// invalidate the current cursor position before scrolling
1521	_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
1522
1523	// scroll contents
1524	BRect destRect(Frame().OffsetToCopy(Bounds().LeftTop()));
1525	BRect sourceRect(destRect.OffsetByCopy(0, diff));
1526//debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
1527//sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
1528//destRect.left, destRect.top, destRect.right, destRect.bottom);
1529	CopyBits(sourceRect, destRect);
1530
1531	// sync visible text buffer with text buffer
1532	if (newFirstLine != oldFirstLine || newLastLine != oldLastLine) {
1533		if (newFirstLine != oldFirstLine)
1534{
1535//debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine);
1536			fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine);
1537}
1538		BAutolock _(fTextBuffer);
1539		if (diff < 0)
1540			_SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1);
1541		else
1542			_SynchronizeWithTextBuffer(oldLastLine + 1, newLastLine);
1543	}
1544}
1545
1546
1547BHandler*
1548TermView::ResolveSpecifier(BMessage *message, int32 index, BMessage *specifier,
1549				int32 what, const char *property)
1550{
1551	BHandler *target = this;
1552	BPropertyInfo propInfo(sPropList);
1553	if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK)
1554		target = BView::ResolveSpecifier(message, index, specifier, what, property);
1555
1556	return target;
1557}
1558
1559
1560//! Gets dropped file full path and display it at cursor position.
1561void
1562TermView::_DoFileDrop(entry_ref &ref)
1563{
1564	BEntry ent(&ref);
1565	BPath path(&ent);
1566	BString string(path.Path());
1567
1568	string.CharacterEscape(" ~`#$&*()\\|[]{};'\"<>?!",'\\');
1569	_WritePTY(string.String(), string.Length());
1570}
1571
1572
1573/*!	Text buffer must already be locked.
1574*/
1575void
1576TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
1577	int32 visibleDirtyBottom)
1578{
1579	TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo();
1580	int32 linesScrolled = info.linesScrolled;
1581
1582//debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, "
1583//"scrolled: %ld, visible dirty: %ld - %ld\n", info.dirtyTop, info.dirtyBottom,
1584//info.linesScrolled, visibleDirtyTop, visibleDirtyBottom);
1585
1586	bigtime_t now = system_time();
1587	bigtime_t timeElapsed = now - fLastSyncTime;
1588	if (timeElapsed > 2 * kSyncUpdateGranularity) {
1589		// last sync was ages ago
1590		fLastSyncTime = now;
1591		fScrolledSinceLastSync = linesScrolled;
1592	}
1593
1594	if (fSyncRunner == NULL) {
1595		// We consider clocked syncing when more than a full screen height has
1596		// been scrolled in less than a sync update period. Once we're
1597		// actively considering it, the same condition will convince us to
1598		// actually do it.
1599		if (fScrolledSinceLastSync + linesScrolled <= fTermRows) {
1600			// Condition doesn't hold yet. Reset if time is up, or otherwise
1601			// keep counting.
1602			if (timeElapsed > kSyncUpdateGranularity) {
1603				fConsiderClockedSync = false;
1604				fLastSyncTime = now;
1605				fScrolledSinceLastSync = linesScrolled;
1606			} else
1607				fScrolledSinceLastSync += linesScrolled;
1608		} else if (fConsiderClockedSync) {
1609			// We are convinced -- create the sync runner.
1610			fLastSyncTime = now;
1611			fScrolledSinceLastSync = 0;
1612
1613			BMessage message(MSG_TERMINAL_BUFFER_CHANGED);
1614			fSyncRunner = new(std::nothrow) BMessageRunner(BMessenger(this),
1615				&message, kSyncUpdateGranularity);
1616			if (fSyncRunner != NULL && fSyncRunner->InitCheck() == B_OK)
1617				return;
1618
1619			delete fSyncRunner;
1620			fSyncRunner = NULL;
1621		} else {
1622			// Looks interesting so far. Reset the counts and consider clocked
1623			// syncing.
1624			fConsiderClockedSync = true;
1625			fLastSyncTime = now;
1626			fScrolledSinceLastSync = 0;
1627		}
1628	} else if (timeElapsed < kSyncUpdateGranularity) {
1629		// sync time not passed yet -- keep counting
1630		fScrolledSinceLastSync += linesScrolled;
1631		return;
1632	} else if (fScrolledSinceLastSync + linesScrolled <= fTermRows) {
1633		// time's up, but not enough happened
1634		delete fSyncRunner;
1635		fSyncRunner = NULL;
1636		fLastSyncTime = now;
1637		fScrolledSinceLastSync = linesScrolled;
1638	} else {
1639		// Things are still rolling, but the sync time's up.
1640		fLastSyncTime = now;
1641		fScrolledSinceLastSync = 0;
1642	}
1643
1644	// Simple case first -- complete invalidation.
1645	if (info.invalidateAll) {
1646		Invalidate();
1647		_UpdateScrollBarRange();
1648		_Deselect();
1649
1650		fCursor = fTextBuffer->Cursor();
1651		_ActivateCursor(false);
1652
1653		int32 offset = _LineAt(0);
1654		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset,
1655			offset + fTextBuffer->Height() + 2);
1656
1657		info.Reset();
1658		return;
1659	}
1660
1661	BRect bounds = Bounds();
1662	int32 firstVisible = _LineAt(0);
1663	int32 lastVisible = _LineAt(bounds.bottom);
1664	int32 historySize = fTextBuffer->HistorySize();
1665
1666	bool doScroll = false;
1667	if (linesScrolled > 0) {
1668		_UpdateScrollBarRange();
1669
1670		visibleDirtyTop -= linesScrolled;
1671		visibleDirtyBottom -= linesScrolled;
1672
1673		if (firstVisible < 0) {
1674			firstVisible -= linesScrolled;
1675			lastVisible -= linesScrolled;
1676
1677			float scrollOffset;
1678			if (firstVisible < -historySize) {
1679				firstVisible = -historySize;
1680				doScroll = true;
1681				scrollOffset = -historySize * fFontHeight;
1682				// We need to invalidate the lower linesScrolled lines of the
1683				// visible text buffer, since those will be scrolled up and
1684				// need to be replaced. We just use visibleDirty{Top,Bottom}
1685				// for that purpose. Unless invoked from ScrollTo() (i.e.
1686				// user-initiated scrolling) those are unused. In the unlikely
1687				// case that the user is scrolling at the same time we may
1688				// invalidate too many lines, since we have to extend the given
1689				// region.
1690				// Note that in the firstVisible == 0 case the new lines are
1691				// already in the dirty region, so they will be updated anyway.
1692				if (visibleDirtyTop <= visibleDirtyBottom) {
1693					if (lastVisible < visibleDirtyTop)
1694						visibleDirtyTop = lastVisible;
1695					if (visibleDirtyBottom < lastVisible + linesScrolled)
1696						visibleDirtyBottom = lastVisible + linesScrolled;
1697				} else {
1698					visibleDirtyTop = lastVisible + 1;
1699					visibleDirtyBottom = lastVisible + linesScrolled;
1700				}
1701			} else
1702				scrollOffset = fScrollOffset - linesScrolled * fFontHeight;
1703
1704			_ScrollTo(scrollOffset, false);
1705		} else
1706			doScroll = true;
1707
1708		if (doScroll && lastVisible >= firstVisible
1709			&& !(info.IsDirtyRegionValid() && firstVisible >= info.dirtyTop
1710				&& lastVisible <= info.dirtyBottom)) {
1711			// scroll manually
1712			float scrollBy = linesScrolled * fFontHeight;
1713			BRect destRect(Frame().OffsetToCopy(B_ORIGIN));
1714			BRect sourceRect(destRect.OffsetByCopy(0, scrollBy));
1715
1716			// invalidate the current cursor position before scrolling
1717			_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
1718
1719//debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
1720//sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
1721//destRect.left, destRect.top, destRect.right, destRect.bottom);
1722			CopyBits(sourceRect, destRect);
1723
1724			fVisibleTextBuffer->ScrollBy(linesScrolled);
1725		}
1726
1727		// move selection
1728		if (fSelStart != fSelEnd) {
1729			fSelStart.y -= linesScrolled;
1730			fSelEnd.y -= linesScrolled;
1731			fInitialSelectionStart.y -= linesScrolled;
1732			fInitialSelectionEnd.y -= linesScrolled;
1733
1734			if (fSelStart.y < -historySize)
1735				_Deselect();
1736		}
1737	}
1738
1739	// invalidate dirty region
1740	if (info.IsDirtyRegionValid()) {
1741		_InvalidateTextRect(0, info.dirtyTop, fTextBuffer->Width() - 1,
1742			info.dirtyBottom);
1743
1744		// clear the selection, if affected
1745		if (fSelStart != fSelEnd) {
1746			// TODO: We're clearing the selection more often than necessary --
1747			// to avoid that, we'd also need to track the x coordinates of the
1748			// dirty range.
1749			int32 selectionBottom = fSelEnd.x > 0 ? fSelEnd.y : fSelEnd.y - 1;
1750			if (fSelStart.y <= info.dirtyBottom
1751				&& info.dirtyTop <= selectionBottom) {
1752				_Deselect();
1753			}
1754		}
1755	}
1756
1757	if (visibleDirtyTop <= visibleDirtyBottom)
1758		info.ExtendDirtyRegion(visibleDirtyTop, visibleDirtyBottom);
1759
1760	if (linesScrolled != 0 || info.IsDirtyRegionValid()) {
1761		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, firstVisible,
1762			info.dirtyTop, info.dirtyBottom);
1763	}
1764
1765	// invalidate cursor, if it changed
1766	TermPos cursor = fTextBuffer->Cursor();
1767	if (fCursor != cursor || linesScrolled != 0) {
1768		// Before we scrolled we did already invalidate the old cursor.
1769		if (!doScroll)
1770			_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
1771		fCursor = cursor;
1772		_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
1773		_ActivateCursor(false);
1774	}
1775
1776	info.Reset();
1777}
1778
1779
1780/*!	Write strings to PTY device. If encoding system isn't UTF8, change
1781	encoding to UTF8 before writing PTY.
1782*/
1783void
1784TermView::_WritePTY(const char* text, int32 numBytes)
1785{
1786	if (fEncoding != M_UTF8) {
1787		while (numBytes > 0) {
1788			char buffer[1024];
1789			int32 bufferSize = sizeof(buffer);
1790			int32 sourceSize = numBytes;
1791			int32 state = 0;
1792			if (convert_to_utf8(fEncoding, text, &sourceSize, buffer,
1793					&bufferSize, &state) != B_OK || bufferSize == 0) {
1794				break;
1795			}
1796
1797			fShell->Write(buffer, bufferSize);
1798			text += sourceSize;
1799			numBytes -= sourceSize;
1800		}
1801	} else {
1802		fShell->Write(text, numBytes);
1803	}
1804}
1805
1806
1807void
1808TermView::MouseDown(BPoint where)
1809{
1810	if (!IsFocus())
1811		MakeFocus();
1812
1813	int32 buttons;
1814	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
1815
1816	fMouseButtons = buttons;
1817
1818	// paste button
1819	if ((buttons & (B_SECONDARY_MOUSE_BUTTON | B_TERTIARY_MOUSE_BUTTON)) != 0) {
1820		Paste(gMouseClipboard);
1821		return;
1822	}
1823
1824	// Select Region
1825	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
1826		int32 mod, clicks;
1827		Window()->CurrentMessage()->FindInt32("modifiers", &mod);
1828		Window()->CurrentMessage()->FindInt32("clicks", &clicks);
1829
1830		if (_HasSelection()) {
1831			TermPos inPos = _ConvertToTerminal(where);
1832			if (_CheckSelectedRegion(inPos)) {
1833				if (mod & B_CONTROL_KEY) {
1834					BPoint p;
1835					uint32 bt;
1836					do {
1837						GetMouse(&p, &bt);
1838
1839						if (bt == 0) {
1840							_Deselect();
1841							return;
1842						}
1843
1844						snooze(40000);
1845
1846					} while (abs((int)(where.x - p.x)) < 4
1847						&& abs((int)(where.y - p.y)) < 4);
1848
1849					InitiateDrag();
1850					return;
1851				}
1852			}
1853		}
1854
1855		// If mouse has a lot of movement, disable double/triple click.
1856		/*BPoint inPoint = fClickPoint - where;
1857		if (abs((int)inPoint.x) > 16 || abs((int)inPoint.y) > 16)
1858			clicks = 1;
1859		*/
1860
1861		SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS,
1862				B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS);
1863
1864		TermPos clickPos = _ConvertToTerminal(where);
1865
1866		if (mod & B_SHIFT_KEY) {
1867			fInitialSelectionStart = clickPos;
1868			fInitialSelectionEnd = clickPos;
1869			_ExtendSelection(fInitialSelectionStart, true, false);
1870		} else {
1871			_Deselect();
1872			fInitialSelectionStart = clickPos;
1873			fInitialSelectionEnd = clickPos;
1874		}
1875
1876		// If clicks larger than 3, reset mouse click counter.
1877		clicks = (clicks - 1) % 3 + 1;
1878
1879		switch (clicks) {
1880			case 1:
1881				fMouseTracking = true;
1882				fSelectGranularity = SELECT_CHARS;
1883	      		break;
1884
1885			case 2:
1886				_SelectWord(where, (mod & B_SHIFT_KEY) != 0, false);
1887				fMouseTracking = true;
1888				fSelectGranularity = SELECT_WORDS;
1889				break;
1890
1891			case 3:
1892	 			_SelectLine(where, (mod & B_SHIFT_KEY) != 0, false);
1893				fMouseTracking = true;
1894				fSelectGranularity = SELECT_LINES;
1895				break;
1896		}
1897		return;
1898  	}
1899
1900	BView::MouseDown(where);
1901}
1902
1903
1904void
1905TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message)
1906{
1907	BView::MouseMoved(where, transit, message);
1908	if (!fMouseTracking)
1909		return;
1910
1911	bool doAutoScroll = false;
1912
1913	if (where.y < 0) {
1914		doAutoScroll = true;
1915		fAutoScrollSpeed = where.y;
1916		where.x = 0;
1917		where.y = 0;
1918	}
1919
1920	BRect bounds(Bounds());
1921	if (where.y > bounds.bottom) {
1922		doAutoScroll = true;
1923		fAutoScrollSpeed = where.y - bounds.bottom;
1924		where.x = bounds.right;
1925		where.y = bounds.bottom;
1926	}
1927
1928	if (doAutoScroll) {
1929		if (fAutoScrollRunner == NULL) {
1930			BMessage message(kAutoScroll);
1931			fAutoScrollRunner = new (std::nothrow) BMessageRunner(
1932				BMessenger(this), &message, 10000);
1933		}
1934	} else {
1935		delete fAutoScrollRunner;
1936		fAutoScrollRunner = NULL;
1937	}
1938
1939	switch (fSelectGranularity) {
1940		case SELECT_CHARS:
1941		{
1942			// If we just start selecting, we first select the initially
1943			// hit char, so that we get a proper initial selection -- the char
1944			// in question, which will thus always be selected, regardless of
1945			// whether selecting forward or backward.
1946			if (fInitialSelectionStart == fInitialSelectionEnd) {
1947				_Select(fInitialSelectionStart, fInitialSelectionEnd, true,
1948					true);
1949			}
1950
1951			_ExtendSelection(_ConvertToTerminal(where), true, true);
1952      		break;
1953		}
1954		case SELECT_WORDS:
1955			_SelectWord(where, true, true);
1956      		break;
1957		case SELECT_LINES:
1958			_SelectLine(where, true, true);
1959      		break;
1960	  }
1961}
1962
1963
1964void
1965TermView::MouseUp(BPoint where)
1966{
1967	BView::MouseUp(where);
1968	fMouseTracking = false;
1969
1970	if (fAutoScrollRunner != NULL) {
1971		delete fAutoScrollRunner;
1972		fAutoScrollRunner = NULL;
1973	}
1974
1975	// When releasing the first mouse button, we copy the selected text to the
1976	// clipboard.
1977	int32 buttons;
1978	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
1979	if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0
1980		&& (fMouseButtons & B_PRIMARY_MOUSE_BUTTON) != 0) {
1981		Copy(gMouseClipboard);
1982	}
1983
1984	fMouseButtons = buttons;
1985}
1986
1987
1988// Select a range of text
1989void
1990TermView::_Select(TermPos start, TermPos end, bool inclusive,
1991	bool setInitialSelection)
1992{
1993	BAutolock _(fTextBuffer);
1994
1995	_SynchronizeWithTextBuffer(0, -1);
1996
1997	if (end < start)
1998		std::swap(start, end);
1999
2000	if (inclusive)
2001		end.x++;
2002
2003//debug_printf("TermView::_Select(): (%ld, %ld) - (%ld, %ld)\n", start.x,
2004//start.y, end.x, end.y);
2005
2006	if (start.x < 0)
2007		start.x = 0;
2008	if (end.x >= fTermColumns)
2009		end.x = fTermColumns;
2010
2011	TermPos minPos(0, -fTextBuffer->HistorySize());
2012	TermPos maxPos(0, fTextBuffer->Height());
2013	start = restrict_value(start, minPos, maxPos);
2014	end = restrict_value(end, minPos, maxPos);
2015
2016	// if the end is past the end of the line, select the line break, too
2017	if (fTextBuffer->LineLength(end.y) < end.x
2018			&& end.y < fTextBuffer->Height()) {
2019		end.y++;
2020		end.x = 0;
2021	}
2022
2023	if (fTextBuffer->IsFullWidthChar(start.y, start.x)) {
2024		start.x--;
2025		if (start.x < 0)
2026			start.x = 0;
2027	}
2028
2029	if (fTextBuffer->IsFullWidthChar(end.y, end.x)) {
2030		end.x++;
2031		if (end.x >= fTermColumns)
2032			end.x = fTermColumns;
2033	}
2034
2035	if (fSelStart != fSelEnd)
2036		_InvalidateTextRange(fSelStart, fSelEnd);
2037
2038	fSelStart = start;
2039	fSelEnd = end;
2040
2041	if (setInitialSelection) {
2042		fInitialSelectionStart = fSelStart;
2043		fInitialSelectionEnd = fSelEnd;
2044	}
2045
2046	_InvalidateTextRange(fSelStart, fSelEnd);
2047}
2048
2049
2050// extend selection (shift + mouse click)
2051void
2052TermView::_ExtendSelection(TermPos pos, bool inclusive,
2053	bool useInitialSelection)
2054{
2055	if (!useInitialSelection && !_HasSelection())
2056		return;
2057
2058	TermPos start = fSelStart;
2059	TermPos end = fSelEnd;
2060
2061	if (useInitialSelection) {
2062		start = fInitialSelectionStart;
2063		end = fInitialSelectionEnd;
2064	}
2065
2066	if (inclusive) {
2067		if (pos >= start && pos >= end)
2068			pos.x++;
2069	}
2070
2071	if (pos < start)
2072		_Select(pos, end, false, !useInitialSelection);
2073	else if (pos > end)
2074		_Select(start, pos, false, !useInitialSelection);
2075	else if (useInitialSelection)
2076		_Select(start, end, false, false);
2077}
2078
2079
2080// clear the selection.
2081void
2082TermView::_Deselect()
2083{
2084//debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection());
2085	if (!_HasSelection())
2086		return;
2087
2088	_InvalidateTextRange(fSelStart, fSelEnd);
2089
2090	fSelStart.SetTo(0, 0);
2091	fSelEnd.SetTo(0, 0);
2092	fInitialSelectionStart.SetTo(0, 0);
2093	fInitialSelectionEnd.SetTo(0, 0);
2094}
2095
2096
2097bool
2098TermView::_HasSelection() const
2099{
2100	return fSelStart != fSelEnd;
2101}
2102
2103
2104void
2105TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection)
2106{
2107	BAutolock _(fTextBuffer);
2108
2109	TermPos pos = _ConvertToTerminal(where);
2110	TermPos start, end;
2111	if (!fTextBuffer->FindWord(pos, fCharClassifier, true, start, end))
2112		return;
2113
2114	if (extend) {
2115		if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart))
2116			_ExtendSelection(start, false, useInitialSelection);
2117		else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd))
2118			_ExtendSelection(end, false, useInitialSelection);
2119		else if (useInitialSelection)
2120			_Select(start, end, false, false);
2121	} else
2122		_Select(start, end, false, !useInitialSelection);
2123}
2124
2125
2126void
2127TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection)
2128{
2129	TermPos start = TermPos(0, _ConvertToTerminal(where).y);
2130	TermPos end = TermPos(0, start.y + 1);
2131
2132	if (extend) {
2133		if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart))
2134			_ExtendSelection(start, false, useInitialSelection);
2135		else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd))
2136			_ExtendSelection(end, false, useInitialSelection);
2137		else if (useInitialSelection)
2138			_Select(start, end, false, false);
2139	} else
2140		_Select(start, end, false, !useInitialSelection);
2141}
2142
2143
2144void
2145TermView::_AutoScrollUpdate()
2146{
2147	if (fMouseTracking && fAutoScrollRunner != NULL && fScrollBar != NULL) {
2148		float value = fScrollBar->Value();
2149		_ScrollTo(value + fAutoScrollSpeed, true);
2150		if (fAutoScrollSpeed < 0) {
2151			_ExtendSelection(_ConvertToTerminal(BPoint(0, 0)), true, true);
2152		} else {
2153			_ExtendSelection(_ConvertToTerminal(Bounds().RightBottom()), true,
2154				true);
2155		}
2156	}
2157}
2158
2159
2160bool
2161TermView::_CheckSelectedRegion(const TermPos &pos) const
2162{
2163	return pos >= fSelStart && pos < fSelEnd;
2164}
2165
2166
2167bool
2168TermView::_CheckSelectedRegion(int32 row, int32 firstColumn,
2169	int32& lastColumn) const
2170{
2171	if (fSelStart == fSelEnd)
2172		return false;
2173
2174	if (row == fSelStart.y && firstColumn < fSelStart.x
2175			&& lastColumn >= fSelStart.x) {
2176		// region starts before the selection, but intersects with it
2177		lastColumn = fSelStart.x - 1;
2178		return false;
2179	}
2180
2181	if (row == fSelEnd.y && firstColumn < fSelEnd.x
2182			&& lastColumn >= fSelEnd.x) {
2183		// region starts in the selection, but exceeds the end
2184		lastColumn = fSelEnd.x - 1;
2185		return true;
2186	}
2187
2188	TermPos pos(firstColumn, row);
2189	return pos >= fSelStart && pos < fSelEnd;
2190}
2191
2192
2193void
2194TermView::GetFrameSize(float *width, float *height)
2195{
2196	int32 historySize;
2197	{
2198		BAutolock _(fTextBuffer);
2199		historySize = fTextBuffer->HistorySize();
2200	}
2201
2202	if (width != NULL)
2203		*width = fTermColumns * fFontWidth;
2204
2205	if (height != NULL)
2206		*height = (fTermRows + historySize) * fFontHeight;
2207}
2208
2209
2210// Find a string, and select it if found
2211bool
2212TermView::Find(const BString &str, bool forwardSearch, bool matchCase,
2213	bool matchWord)
2214{
2215	BAutolock _(fTextBuffer);
2216	_SynchronizeWithTextBuffer(0, -1);
2217
2218	TermPos start;
2219	if (_HasSelection()) {
2220		if (forwardSearch)
2221			start = fSelEnd;
2222		else
2223			start = fSelStart;
2224	} else {
2225		// search from the very beginning/end
2226		if (forwardSearch)
2227			start = TermPos(0, -fTextBuffer->HistorySize());
2228		else
2229			start = TermPos(0, fTextBuffer->Height());
2230	}
2231
2232	TermPos matchStart, matchEnd;
2233	if (!fTextBuffer->Find(str.String(), start, forwardSearch, matchCase,
2234			matchWord, matchStart, matchEnd)) {
2235		return false;
2236	}
2237
2238	_Select(matchStart, matchEnd, false, true);
2239	_ScrollToRange(fSelStart, fSelEnd);
2240
2241	return true;
2242}
2243
2244
2245//! Get the selected text and copy to str
2246void
2247TermView::GetSelection(BString &str)
2248{
2249	str.SetTo("");
2250	BAutolock _(fTextBuffer);
2251	fTextBuffer->GetStringFromRegion(str, fSelStart, fSelEnd);
2252}
2253
2254
2255void
2256TermView::NotifyQuit(int32 reason)
2257{
2258	// implemented in subclasses
2259}
2260
2261
2262void
2263TermView::CheckShellGone()
2264{
2265	if (!fShell)
2266		return;
2267
2268	// check, if the shell does still live
2269	pid_t pid = fShell->ProcessID();
2270	team_info info;
2271	if (get_team_info(pid, &info) == B_BAD_TEAM_ID) {
2272		// the shell is gone
2273		NotifyQuit(0);
2274	}
2275}
2276
2277
2278void
2279TermView::InitiateDrag()
2280{
2281	BAutolock _(fTextBuffer);
2282
2283	BString copyStr("");
2284	fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd);
2285
2286	BMessage message(B_MIME_DATA);
2287	message.AddData("text/plain", B_MIME_TYPE, copyStr.String(),
2288		copyStr.Length());
2289
2290	BPoint start = _ConvertFromTerminal(fSelStart);
2291	BPoint end = _ConvertFromTerminal(fSelEnd);
2292
2293	BRect rect;
2294	if (fSelStart.y == fSelEnd.y)
2295		rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight);
2296	else
2297		rect.Set(0, start.y, fTermColumns * fFontWidth, end.y + fFontHeight);
2298
2299	rect = rect & Bounds();
2300
2301	DragMessage(&message, rect);
2302}
2303
2304
2305void
2306TermView::_AboutRequested()
2307{
2308	BAlert *alert = new (std::nothrow) BAlert("about",
2309		"Terminal\n"
2310		"\twritten by Kazuho Okui and Takashi Murai\n"
2311		"\tupdated by Kian Duffy and others\n\n"
2312		"\tCopyright " B_UTF8_COPYRIGHT "2003-2008, Haiku.\n", "Ok");
2313	if (alert != NULL)
2314		alert->Go();
2315}
2316
2317
2318void
2319TermView::_ScrollTo(float y, bool scrollGfx)
2320{
2321	if (!scrollGfx)
2322		fScrollOffset = y;
2323
2324	if (fScrollBar != NULL)
2325		fScrollBar->SetValue(y);
2326	else
2327		ScrollTo(BPoint(0, y));
2328}
2329
2330
2331void
2332TermView::_ScrollToRange(TermPos start, TermPos end)
2333{
2334	if (start > end)
2335		std::swap(start, end);
2336
2337	float startY = _LineOffset(start.y);
2338	float endY = _LineOffset(end.y) + fFontHeight - 1;
2339	float height = Bounds().Height();
2340
2341	if (endY - startY > height) {
2342		// The range is greater than the height. Scroll to the closest border.
2343
2344		// already as good as it gets?
2345		if (startY <= 0 && endY >= height)
2346			return;
2347
2348		if (startY > 0) {
2349			// scroll down to align the start with the top of the view
2350			_ScrollTo(fScrollOffset + startY, true);
2351		} else {
2352			// scroll up to align the end with the bottom of the view
2353			_ScrollTo(fScrollOffset + endY - height, true);
2354		}
2355	} else {
2356		// The range is smaller than the height.
2357
2358		// already visible?
2359		if (startY >= 0 && endY <= height)
2360			return;
2361
2362		if (startY < 0) {
2363			// scroll up to make the start visible
2364			_ScrollTo(fScrollOffset + startY, true);
2365		} else {
2366			// scroll down to make the end visible
2367			_ScrollTo(fScrollOffset + endY - height, true);
2368		}
2369	}
2370}
2371