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