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