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