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