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