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