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