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