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