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