TermView.cpp revision fe55edca
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	fAllowBold = 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(float* _width, float* _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 = int((rect.IntegerWidth() + 1) / fFontWidth);
635	int rows = int((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	float 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		float tmpWidth = 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	bool blinking = PrefHandler::Default()->getBool(PREF_BLINK_CURSOR);
778	SwitchCursorBlinking(blinking);
779
780	fEmulateBold = PrefHandler::Default() == NULL ? false
781		: PrefHandler::Default()->getBool(PREF_EMULATE_BOLD);
782
783	fAllowBold = PrefHandler::Default() == NULL ? false
784		: PrefHandler::Default()->getBool(PREF_ALLOW_BOLD);
785
786	_ScrollTo(0, false);
787	if (fScrollBar != NULL)
788		fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
789}
790
791
792void
793TermView::SetScrollBar(BScrollBar *scrollBar)
794{
795	fScrollBar = scrollBar;
796	if (fScrollBar != NULL)
797		fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
798}
799
800
801void
802TermView::SwitchCursorBlinking(bool blinkingOn)
803{
804	fCursorBlinking = blinkingOn;
805	if (blinkingOn) {
806		if (fCursorBlinkRunner == NULL) {
807			BMessage blinkMessage(kBlinkCursor);
808			fCursorBlinkRunner = new (std::nothrow) BMessageRunner(
809				BMessenger(this), &blinkMessage, kCursorBlinkInterval);
810		}
811	} else {
812		// make sure the cursor becomes visible
813		fCursorState = 0;
814		_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
815		delete fCursorBlinkRunner;
816		fCursorBlinkRunner = NULL;
817	}
818}
819
820
821void
822TermView::Copy(BClipboard *clipboard)
823{
824	BAutolock _(fTextBuffer);
825
826	if (!_HasSelection())
827		return;
828
829	BString copyStr;
830	fTextBuffer->GetStringFromRegion(copyStr, fSelection.Start(),
831		fSelection.End());
832
833	if (clipboard->Lock()) {
834		BMessage *clipMsg = NULL;
835		clipboard->Clear();
836
837		if ((clipMsg = clipboard->Data()) != NULL) {
838			clipMsg->AddData("text/plain", B_MIME_TYPE, copyStr.String(),
839				copyStr.Length());
840			clipboard->Commit();
841		}
842		clipboard->Unlock();
843	}
844}
845
846
847void
848TermView::Paste(BClipboard *clipboard)
849{
850	if (clipboard->Lock()) {
851		BMessage *clipMsg = clipboard->Data();
852		const char* text;
853		ssize_t numBytes;
854		if (clipMsg->FindData("text/plain", B_MIME_TYPE,
855				(const void**)&text, &numBytes) == B_OK ) {
856			_WritePTY(text, numBytes);
857		}
858
859		clipboard->Unlock();
860
861		_ScrollTo(0, true);
862	}
863}
864
865
866void
867TermView::SelectAll()
868{
869	BAutolock _(fTextBuffer);
870
871	_Select(TermPos(0, -fTextBuffer->HistorySize()),
872		TermPos(0, fTextBuffer->Height()), false, true);
873}
874
875
876void
877TermView::Clear()
878{
879	_Deselect();
880
881	{
882		BAutolock _(fTextBuffer);
883		fTextBuffer->Clear(true);
884	}
885	fVisibleTextBuffer->Clear(true);
886
887//debug_printf("Invalidate()\n");
888	Invalidate();
889
890	_ScrollTo(0, false);
891	if (fScrollBar) {
892		fScrollBar->SetRange(0, 0);
893		fScrollBar->SetProportion(1);
894	}
895}
896
897
898//! Draw region
899void
900TermView::_InvalidateTextRange(TermPos start, TermPos end)
901{
902	if (end < start)
903		std::swap(start, end);
904
905	if (start.y == end.y) {
906		_InvalidateTextRect(start.x, start.y, end.x, end.y);
907	} else {
908		_InvalidateTextRect(start.x, start.y, fColumns, start.y);
909
910		if (end.y - start.y > 0)
911			_InvalidateTextRect(0, start.y + 1, fColumns, end.y - 1);
912
913		_InvalidateTextRect(0, end.y, end.x, end.y);
914	}
915}
916
917
918status_t
919TermView::_AttachShell(Shell *shell)
920{
921	if (shell == NULL)
922		return B_BAD_VALUE;
923
924	fShell = shell;
925
926	return fShell->AttachBuffer(TextBuffer());
927}
928
929
930void
931TermView::_DetachShell()
932{
933	fShell->DetachBuffer();
934	fShell = NULL;
935}
936
937
938void
939TermView::_Activate()
940{
941	fActive = true;
942	SwitchCursorBlinking(fCursorBlinking);
943}
944
945
946void
947TermView::_Deactivate()
948{
949	// make sure the cursor becomes visible
950	fCursorState = 0;
951	_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
952
953	SwitchCursorBlinking(false);
954
955	fActive = false;
956}
957
958
959//! Draw part of a line in the given view.
960void
961TermView::_DrawLinePart(float x1, float y1, uint32 attr, char *buf,
962	int32 width, Highlight* highlight, bool cursor, BView *inView)
963{
964	if (highlight != NULL)
965		attr = highlight->Highlighter()->AdjustTextAttributes(attr);
966
967	inView->SetFont(IS_BOLD(attr) && !fEmulateBold && fAllowBold
968		? &fBoldFont : &fHalfFont);
969
970	// Set pen point
971	float x2 = x1 + fFontWidth * width;
972	float y2 = y1 + fFontHeight;
973
974	rgb_color rgb_fore = fTextForeColor;
975	rgb_color rgb_back = fTextBackColor;
976
977	// color attribute
978	int forecolor = IS_FORECOLOR(attr);
979	int backcolor = IS_BACKCOLOR(attr);
980
981	if (IS_FORESET(attr))
982		rgb_fore = fTextBuffer->PaletteColor(forecolor);
983	if (IS_BACKSET(attr))
984		rgb_back = fTextBuffer->PaletteColor(backcolor);
985
986	// Selection check.
987	if (cursor) {
988		rgb_fore = fCursorForeColor;
989		rgb_back = fCursorBackColor;
990	} else if (highlight != NULL) {
991		rgb_fore = highlight->Highlighter()->ForegroundColor();
992		rgb_back = highlight->Highlighter()->BackgroundColor();
993	} else {
994		// Reverse attribute(If selected area, don't reverse color).
995		if (IS_INVERSE(attr)) {
996			rgb_color rgb_tmp = rgb_fore;
997			rgb_fore = rgb_back;
998			rgb_back = rgb_tmp;
999		}
1000	}
1001
1002	// Fill color at Background color and set low color.
1003	inView->SetHighColor(rgb_back);
1004	inView->FillRect(BRect(x1, y1, x2 - 1, y2 - 1));
1005	inView->SetLowColor(rgb_back);
1006	inView->SetHighColor(rgb_fore);
1007
1008	// Draw character.
1009	if (IS_BOLD(attr)) {
1010		if (fEmulateBold) {
1011			inView->MovePenTo(x1 - 1, y1 + fFontAscent - 1);
1012			inView->DrawString((char *)buf);
1013			inView->SetDrawingMode(B_OP_BLEND);
1014		} else {
1015			rgb_color bright = rgb_fore;
1016
1017			bright.red = saturated_add<uint8>(bright.red, 64);
1018			bright.green = saturated_add<uint8>(bright.green, 64);
1019			bright.blue = saturated_add<uint8>(bright.blue, 64);
1020
1021			inView->SetHighColor(bright);
1022		}
1023	}
1024
1025	inView->MovePenTo(x1, y1 + fFontAscent);
1026	inView->DrawString((char *)buf);
1027	inView->SetDrawingMode(B_OP_COPY);
1028
1029	// underline attribute
1030	if (IS_UNDER(attr)) {
1031		inView->MovePenTo(x1, y1 + fFontAscent);
1032		inView->StrokeLine(BPoint(x1 , y1 + fFontAscent),
1033			BPoint(x2 , y1 + fFontAscent));
1034	}
1035}
1036
1037
1038/*!	Caller must have locked fTextBuffer.
1039*/
1040void
1041TermView::_DrawCursor()
1042{
1043	BRect rect(fFontWidth * fCursor.x, _LineOffset(fCursor.y), 0, 0);
1044	rect.right = rect.left + fFontWidth - 1;
1045	rect.bottom = rect.top + fFontHeight - 1;
1046	int32 firstVisible = _LineAt(0);
1047
1048	UTF8Char character;
1049	uint32 attr = 0;
1050
1051	bool cursorVisible = _IsCursorVisible();
1052
1053	if (cursorVisible) {
1054		switch (fCursorStyle) {
1055			case UNDERLINE_CURSOR:
1056				rect.top = rect.bottom - 2;
1057				break;
1058			case IBEAM_CURSOR:
1059				rect.right = rect.left + 1;
1060				break;
1061			case BLOCK_CURSOR:
1062			default:
1063				break;
1064		}
1065	}
1066
1067	Highlight* highlight = _CheckHighlightRegion(TermPos(fCursor.x, fCursor.y));
1068	if (fVisibleTextBuffer->GetChar(fCursor.y - firstVisible, fCursor.x,
1069			character, attr) == A_CHAR
1070			&& (fCursorStyle == BLOCK_CURSOR || !cursorVisible)) {
1071
1072		int32 width = IS_WIDTH(attr) ? FULL_WIDTH : HALF_WIDTH;
1073		char buffer[5];
1074		int32 bytes = UTF8Char::ByteCount(character.bytes[0]);
1075		memcpy(buffer, character.bytes, bytes);
1076		buffer[bytes] = '\0';
1077
1078		_DrawLinePart(fCursor.x * fFontWidth, (int32)rect.top, attr, buffer,
1079			width, highlight, cursorVisible, this);
1080	} else {
1081		if (highlight != NULL)
1082			SetHighColor(highlight->Highlighter()->BackgroundColor());
1083		else if (cursorVisible)
1084			SetHighColor(fCursorBackColor );
1085		else {
1086			uint32 count = 0;
1087			rgb_color rgb_back = fTextBackColor;
1088			if (fTextBuffer->IsAlternateScreenActive())
1089				// alternate screen uses cell attributes beyond the line ends
1090				fTextBuffer->GetCellAttributes(
1091						fCursor.y, fCursor.x, attr, count);
1092			else
1093				attr = fVisibleTextBuffer->GetLineColor(
1094						fCursor.y - firstVisible);
1095
1096			if (IS_BACKSET(attr))
1097				rgb_back = fTextBuffer->PaletteColor(IS_BACKCOLOR(attr));
1098			SetHighColor(rgb_back);
1099		}
1100
1101		if (IS_WIDTH(attr) && fCursorStyle != IBEAM_CURSOR)
1102			rect.right += fFontWidth;
1103
1104		FillRect(rect);
1105	}
1106}
1107
1108
1109bool
1110TermView::_IsCursorVisible() const
1111{
1112	return !fCursorHidden && fCursorState < kCursorVisibleIntervals;
1113}
1114
1115
1116void
1117TermView::_BlinkCursor()
1118{
1119	bool wasVisible = _IsCursorVisible();
1120
1121	if (!wasVisible && fInline && fInline->IsActive())
1122		return;
1123
1124	bigtime_t now = system_time();
1125	if (Window()->IsActive() && now - fLastActivityTime >= kCursorBlinkInterval)
1126		fCursorState = (fCursorState + 1) % kCursorBlinkIntervals;
1127	else
1128		fCursorState = 0;
1129
1130	if (wasVisible != _IsCursorVisible())
1131		_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
1132}
1133
1134
1135void
1136TermView::_ActivateCursor(bool invalidate)
1137{
1138	fLastActivityTime = system_time();
1139	if (invalidate && fCursorState != 0)
1140		_BlinkCursor();
1141	else
1142		fCursorState = 0;
1143}
1144
1145
1146//! Update scroll bar range and knob size.
1147void
1148TermView::_UpdateScrollBarRange()
1149{
1150	if (fScrollBar == NULL)
1151		return;
1152
1153	int32 historySize;
1154	{
1155		BAutolock _(fTextBuffer);
1156		historySize = fTextBuffer->HistorySize();
1157	}
1158
1159	float viewHeight = fRows * fFontHeight;
1160	float historyHeight = (float)historySize * fFontHeight;
1161
1162//debug_printf("TermView::_UpdateScrollBarRange(): history: %ld, range: %f - 0\n",
1163//historySize, -historyHeight);
1164
1165	fScrollBar->SetRange(-historyHeight, 0);
1166	if (historySize > 0)
1167		fScrollBar->SetProportion(viewHeight / (viewHeight + historyHeight));
1168}
1169
1170
1171//!	Handler for SIGWINCH
1172void
1173TermView::_UpdateSIGWINCH()
1174{
1175	if (fFrameResized) {
1176		fShell->UpdateWindowSize(fRows, fColumns);
1177		fFrameResized = false;
1178	}
1179}
1180
1181
1182void
1183TermView::AttachedToWindow()
1184{
1185	fMouseButtons = 0;
1186
1187	_UpdateModifiers();
1188
1189	// update the terminal size because it may have changed while the TermView
1190	// was detached from the window. On such conditions FrameResized was not
1191	// called when the resize occured
1192	SetTermSize(Bounds(), true);
1193	MakeFocus(true);
1194	if (fScrollBar) {
1195		fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
1196		_UpdateScrollBarRange();
1197	}
1198
1199	BMessenger thisMessenger(this);
1200
1201	BMessage message(kUpdateSigWinch);
1202	fWinchRunner = new (std::nothrow) BMessageRunner(thisMessenger,
1203		&message, 500000);
1204
1205	{
1206		TextBufferSyncLocker _(this);
1207		fTextBuffer->SetListener(thisMessenger);
1208		_SynchronizeWithTextBuffer(0, -1);
1209	}
1210
1211	be_clipboard->StartWatching(thisMessenger);
1212}
1213
1214
1215void
1216TermView::DetachedFromWindow()
1217{
1218	be_clipboard->StopWatching(BMessenger(this));
1219
1220	 _NextState(fDefaultState);
1221
1222	delete fWinchRunner;
1223	fWinchRunner = NULL;
1224
1225	delete fCursorBlinkRunner;
1226	fCursorBlinkRunner = NULL;
1227
1228	delete fResizeRunner;
1229	fResizeRunner = NULL;
1230
1231	{
1232		BAutolock _(fTextBuffer);
1233		fTextBuffer->UnsetListener();
1234	}
1235}
1236
1237
1238void
1239TermView::Draw(BRect updateRect)
1240{
1241	int32 x1 = (int32)(updateRect.left / fFontWidth);
1242	int32 x2 = std::min((int)(updateRect.right / fFontWidth), fColumns - 1);
1243
1244	int32 firstVisible = _LineAt(0);
1245	int32 y1 = _LineAt(updateRect.top);
1246	int32 y2 = std::min(_LineAt(updateRect.bottom), (int32)fRows - 1);
1247
1248	// clear the area to the right of the line ends
1249	if (y1 <= y2) {
1250		float clearLeft = fColumns * fFontWidth;
1251		if (clearLeft <= updateRect.right) {
1252			BRect rect(clearLeft, updateRect.top, updateRect.right,
1253				updateRect.bottom);
1254			SetHighColor(fTextBackColor);
1255			FillRect(rect);
1256		}
1257	}
1258
1259	// clear the area below the last line
1260	if (y2 == fRows - 1) {
1261		float clearTop = _LineOffset(fRows);
1262		if (clearTop <= updateRect.bottom) {
1263			BRect rect(updateRect.left, clearTop, updateRect.right,
1264				updateRect.bottom);
1265			SetHighColor(fTextBackColor);
1266			FillRect(rect);
1267		}
1268	}
1269
1270	// draw the affected line parts
1271	if (x1 <= x2) {
1272		uint32 attr = 0;
1273
1274		for (int32 j = y1; j <= y2; j++) {
1275			int32 k = x1;
1276			char buf[fColumns * 4 + 1];
1277
1278			if (fVisibleTextBuffer->IsFullWidthChar(j - firstVisible, k))
1279				k--;
1280
1281			if (k < 0)
1282				k = 0;
1283
1284			for (int32 i = k; i <= x2;) {
1285				int32 lastColumn = x2;
1286				Highlight* highlight = _CheckHighlightRegion(j, i, lastColumn);
1287					// This will clip lastColumn to the selection start or end
1288					// to ensure the selection is not drawn at the same time as
1289					// something else
1290				int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i,
1291					lastColumn, buf, attr);
1292
1293// debug_printf("  fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), highlight: %p\n",
1294// j - firstVisible, i, lastColumn, count, (int)count, buf, highlight);
1295
1296				if (count == 0) {
1297					// No chars to draw : we just fill the rectangle with the
1298					// back color of the last char at the left
1299					int nextColumn = lastColumn + 1;
1300					BRect rect(fFontWidth * i, _LineOffset(j),
1301						fFontWidth * nextColumn - 1, 0);
1302					rect.bottom = rect.top + fFontHeight - 1;
1303
1304					rgb_color rgb_back = highlight != NULL
1305						? highlight->Highlighter()->BackgroundColor()
1306						: fTextBackColor;
1307
1308					if (fTextBuffer->IsAlternateScreenActive()) {
1309						// alternate screen uses cell attributes
1310						// beyond the line ends
1311						uint32 count = 0;
1312						fTextBuffer->GetCellAttributes(j, i, attr, count);
1313						rect.right = rect.left + fFontWidth * count - 1;
1314						nextColumn = i + count;
1315					} else
1316						attr = fVisibleTextBuffer->GetLineColor(j - firstVisible);
1317
1318					if (IS_BACKSET(attr)) {
1319						int backcolor = IS_BACKCOLOR(attr);
1320						rgb_back = fTextBuffer->PaletteColor(backcolor);
1321					}
1322
1323					SetHighColor(rgb_back);
1324					rgb_back = HighColor();
1325					FillRect(rect);
1326
1327					// Go on to the next block
1328					i = nextColumn;
1329					continue;
1330				}
1331
1332				// Note: full-width characters GetString()-ed always
1333				// with count 1, so this hardcoding is safe. From the other
1334				// side - drawing the whole string with one call render the
1335				// characters not aligned to cells grid - that looks much more
1336				// inaccurate for full-width strings than for half-width ones.
1337				if (IS_WIDTH(attr))
1338					count = FULL_WIDTH;
1339
1340				_DrawLinePart(fFontWidth * i, (int32)_LineOffset(j),
1341					attr, buf, count, highlight, false, this);
1342				i += count;
1343			}
1344		}
1345	}
1346
1347	if (fInline && fInline->IsActive())
1348		_DrawInlineMethodString();
1349
1350	if (fCursor >= TermPos(x1, y1) && fCursor <= TermPos(x2, y2))
1351		_DrawCursor();
1352}
1353
1354
1355void
1356TermView::_DoPrint(BRect updateRect)
1357{
1358#if 0
1359	uint32 attr;
1360	uchar buf[1024];
1361
1362	const int numLines = (int)((updateRect.Height()) / fFontHeight);
1363
1364	int y1 = (int)(updateRect.top) / fFontHeight;
1365	y1 = y1 -(fScrBufSize - numLines * 2);
1366	if (y1 < 0)
1367		y1 = 0;
1368
1369	const int y2 = y1 + numLines -1;
1370
1371	const int x1 = (int)(updateRect.left) / fFontWidth;
1372	const int x2 = (int)(updateRect.right) / fFontWidth;
1373
1374	for (int j = y1; j <= y2; j++) {
1375		// If(x1, y1) Buffer is in string full width character,
1376		// alignment start position.
1377
1378		int k = x1;
1379		if (fTextBuffer->IsFullWidthChar(j, k))
1380			k--;
1381
1382		if (k < 0)
1383			k = 0;
1384
1385		for (int i = k; i <= x2;) {
1386			int count = fTextBuffer->GetString(j, i, x2, buf, &attr);
1387			if (count < 0) {
1388				i += abs(count);
1389				continue;
1390			}
1391
1392			_DrawLinePart(fFontWidth * i, fFontHeight * j,
1393				attr, buf, count, false, false, this);
1394			i += count;
1395		}
1396	}
1397#endif	// 0
1398}
1399
1400
1401void
1402TermView::WindowActivated(bool active)
1403{
1404	BView::WindowActivated(active);
1405	if (active && IsFocus()) {
1406		if (!fActive)
1407			_Activate();
1408	} else {
1409		if (fActive)
1410			_Deactivate();
1411	}
1412
1413	_UpdateModifiers();
1414
1415	fActiveState->WindowActivated(active);
1416}
1417
1418
1419void
1420TermView::MakeFocus(bool focusState)
1421{
1422	BView::MakeFocus(focusState);
1423
1424	if (focusState && Window() && Window()->IsActive()) {
1425		if (!fActive)
1426			_Activate();
1427	} else {
1428		if (fActive)
1429			_Deactivate();
1430	}
1431}
1432
1433
1434void
1435TermView::KeyDown(const char *bytes, int32 numBytes)
1436{
1437	_UpdateModifiers();
1438
1439	fActiveState->KeyDown(bytes, numBytes);
1440}
1441
1442
1443void
1444TermView::FrameResized(float width, float height)
1445{
1446//debug_printf("TermView::FrameResized(%f, %f)\n", width, height);
1447	int32 columns = (int32)((width + 1) / fFontWidth);
1448	int32 rows = (int32)((height + 1) / fFontHeight);
1449
1450	if (columns == fColumns && rows == fRows)
1451		return;
1452
1453	bool hasResizeView = fResizeRunner != NULL;
1454	if (!hasResizeView) {
1455		// show the current size in a view
1456		fResizeView = new BStringView(BRect(100, 100, 300, 140), "size", "");
1457		fResizeView->SetAlignment(B_ALIGN_CENTER);
1458		fResizeView->SetFont(be_bold_font);
1459		fResizeView->SetViewColor(fTextBackColor);
1460		fResizeView->SetLowColor(fTextBackColor);
1461		fResizeView->SetHighColor(fTextForeColor);
1462
1463		BMessage message(MSG_REMOVE_RESIZE_VIEW_IF_NEEDED);
1464		fResizeRunner = new(std::nothrow) BMessageRunner(BMessenger(this),
1465			&message, 25000LL);
1466	}
1467
1468	BString text;
1469	text << columns << " x " << rows;
1470	fResizeView->SetText(text.String());
1471	fResizeView->GetPreferredSize(&width, &height);
1472	fResizeView->ResizeTo(width * 1.5, height * 1.5);
1473	fResizeView->MoveTo((Bounds().Width() - fResizeView->Bounds().Width()) / 2,
1474		(Bounds().Height()- fResizeView->Bounds().Height()) / 2);
1475	if (!hasResizeView && fResizeViewDisableCount < 1)
1476		AddChild(fResizeView);
1477
1478	if (fResizeViewDisableCount > 0)
1479		fResizeViewDisableCount--;
1480
1481	SetTermSize(rows, columns, true);
1482}
1483
1484
1485void
1486TermView::MessageReceived(BMessage *msg)
1487{
1488	if (fActiveState->MessageReceived(msg))
1489		return;
1490
1491	entry_ref ref;
1492	const char *ctrl_l = "\x0c";
1493
1494	// first check for any dropped message
1495	if (msg->WasDropped() && (msg->what == B_SIMPLE_DATA
1496			|| msg->what == B_MIME_DATA)) {
1497		char *text;
1498		ssize_t numBytes;
1499		//rgb_color *color;
1500
1501		int32 i = 0;
1502
1503		if (msg->FindRef("refs", i++, &ref) == B_OK) {
1504			// first check if secondary mouse button is pressed
1505			int32 buttons = 0;
1506			msg->FindInt32("buttons", &buttons);
1507
1508			if (buttons == B_SECONDARY_MOUSE_BUTTON) {
1509				// start popup menu
1510				_SecondaryMouseButtonDropped(msg);
1511				return;
1512			}
1513
1514			_DoFileDrop(ref);
1515
1516			while (msg->FindRef("refs", i++, &ref) == B_OK) {
1517				_WritePTY(" ", 1);
1518				_DoFileDrop(ref);
1519			}
1520			return;
1521#if 0
1522		} else if (msg->FindData("RGBColor", B_RGB_COLOR_TYPE,
1523				(const void **)&color, &numBytes) == B_OK
1524				&& numBytes == sizeof(color)) {
1525			// TODO: handle color drop
1526			// maybe only on replicants ?
1527			return;
1528#endif
1529		} else if (msg->FindData("text/plain", B_MIME_TYPE,
1530				(const void **)&text, &numBytes) == B_OK) {
1531			_WritePTY(text, numBytes);
1532			return;
1533		}
1534	}
1535
1536	switch (msg->what) {
1537		case B_SIMPLE_DATA:
1538		case B_REFS_RECEIVED:
1539		{
1540			// handle refs if they weren't dropped
1541			int32 i = 0;
1542			if (msg->FindRef("refs", i++, &ref) == B_OK) {
1543				_DoFileDrop(ref);
1544
1545				while (msg->FindRef("refs", i++, &ref) == B_OK) {
1546					_WritePTY(" ", 1);
1547					_DoFileDrop(ref);
1548				}
1549			} else
1550				BView::MessageReceived(msg);
1551			break;
1552		}
1553
1554		case B_COPY:
1555			Copy(be_clipboard);
1556			break;
1557
1558		case B_PASTE:
1559		{
1560			int32 code;
1561			if (msg->FindInt32("index", &code) == B_OK)
1562				Paste(be_clipboard);
1563			break;
1564		}
1565
1566		case B_CLIPBOARD_CHANGED:
1567			// This message originates from the system clipboard. Overwrite
1568			// the contents of the mouse clipboard with the ones from the
1569			// system clipboard, in case it contains text data.
1570			if (be_clipboard->Lock()) {
1571				if (fMouseClipboard->Lock()) {
1572					BMessage* clipMsgA = be_clipboard->Data();
1573					const char* text;
1574					ssize_t numBytes;
1575					if (clipMsgA->FindData("text/plain", B_MIME_TYPE,
1576							(const void**)&text, &numBytes) == B_OK ) {
1577						fMouseClipboard->Clear();
1578						BMessage* clipMsgB = fMouseClipboard->Data();
1579						clipMsgB->AddData("text/plain", B_MIME_TYPE,
1580							text, numBytes);
1581						fMouseClipboard->Commit();
1582					}
1583					fMouseClipboard->Unlock();
1584				}
1585				be_clipboard->Unlock();
1586			}
1587			break;
1588
1589		case B_SELECT_ALL:
1590			SelectAll();
1591			break;
1592
1593		case B_SET_PROPERTY:
1594		{
1595			int32 i;
1596			int32 encodingID;
1597			BMessage specifier;
1598			if (msg->GetCurrentSpecifier(&i, &specifier) == B_OK
1599				&& strcmp("encoding",
1600					specifier.FindString("property", i)) == 0) {
1601				msg->FindInt32 ("data", &encodingID);
1602				SetEncoding(encodingID);
1603				msg->SendReply(B_REPLY);
1604			} else {
1605				BView::MessageReceived(msg);
1606			}
1607			break;
1608		}
1609
1610		case B_GET_PROPERTY:
1611		{
1612			int32 i;
1613			BMessage specifier;
1614			if (msg->GetCurrentSpecifier(&i, &specifier) == B_OK
1615				&& strcmp("encoding",
1616					specifier.FindString("property", i)) == 0) {
1617				BMessage reply(B_REPLY);
1618				reply.AddInt32("result", Encoding());
1619				msg->SendReply(&reply);
1620			} else if (strcmp("tty",
1621					specifier.FindString("property", i)) == 0) {
1622				BMessage reply(B_REPLY);
1623				reply.AddString("result", TerminalName());
1624				msg->SendReply(&reply);
1625			} else {
1626				BView::MessageReceived(msg);
1627			}
1628			break;
1629		}
1630
1631		case B_MODIFIERS_CHANGED:
1632		{
1633			_UpdateModifiers();
1634			break;
1635		}
1636
1637		case B_INPUT_METHOD_EVENT:
1638		{
1639			int32 opcode;
1640			if (msg->FindInt32("be:opcode", &opcode) == B_OK) {
1641				switch (opcode) {
1642					case B_INPUT_METHOD_STARTED:
1643					{
1644						BMessenger messenger;
1645						if (msg->FindMessenger("be:reply_to",
1646								&messenger) == B_OK) {
1647							fInline = new (std::nothrow)
1648								InlineInput(messenger);
1649						}
1650						break;
1651					}
1652
1653					case B_INPUT_METHOD_STOPPED:
1654						delete fInline;
1655						fInline = NULL;
1656						break;
1657
1658					case B_INPUT_METHOD_CHANGED:
1659						if (fInline != NULL)
1660							_HandleInputMethodChanged(msg);
1661						break;
1662
1663					case B_INPUT_METHOD_LOCATION_REQUEST:
1664						if (fInline != NULL)
1665							_HandleInputMethodLocationRequest();
1666						break;
1667
1668					default:
1669						break;
1670				}
1671			}
1672			break;
1673		}
1674
1675		case B_MOUSE_WHEEL_CHANGED:
1676		{
1677			// overridden to allow scrolling emulation in alternative screen
1678			// mode
1679			BAutolock locker(fTextBuffer);
1680			float deltaY = 0;
1681			if (fTextBuffer->IsAlternateScreenActive()
1682				&& msg->FindFloat("be:wheel_delta_y", &deltaY) == B_OK
1683				&& deltaY != 0) {
1684				// We are in alternative screen mode and have a vertical delta
1685				// we can work with -- emulate scrolling via terminal escape
1686				// sequences.
1687				locker.Unlock();
1688
1689				// scroll pagewise, if one of Option, Command, or Control is
1690				// pressed
1691				int32 steps;
1692				const char* stepString;
1693				if ((modifiers() & B_SHIFT_KEY) != 0) {
1694					// pagewise
1695					stepString = deltaY > 0
1696						? PAGE_DOWN_KEY_CODE : PAGE_UP_KEY_CODE;
1697					steps = abs((int)deltaY);
1698				} else {
1699					// three lines per step
1700					stepString = deltaY > 0
1701						? DOWN_ARROW_KEY_CODE : UP_ARROW_KEY_CODE;
1702					steps = 3 * abs((int)deltaY);
1703				}
1704
1705				// We want to do only a single write(), so compose a string
1706				// repeating the sequence as often as required by the delta.
1707				BString toWrite;
1708				for (int32 i = 0; i <steps; i++)
1709					toWrite << stepString;
1710
1711				_WritePTY(toWrite.String(), toWrite.Length());
1712			} else {
1713				// let the BView's implementation handle the standard scrolling
1714				locker.Unlock();
1715				BView::MessageReceived(msg);
1716			}
1717
1718			break;
1719		}
1720
1721		case MENU_CLEAR_ALL:
1722			Clear();
1723			fShell->Write(ctrl_l, 1);
1724			break;
1725		case kBlinkCursor:
1726			_BlinkCursor();
1727			break;
1728		case kUpdateSigWinch:
1729			_UpdateSIGWINCH();
1730			break;
1731		case kSecondaryMouseDropAction:
1732			_DoSecondaryMouseDropAction(msg);
1733			break;
1734		case MSG_TERMINAL_BUFFER_CHANGED:
1735		{
1736			TextBufferSyncLocker _(this);
1737			_SynchronizeWithTextBuffer(0, -1);
1738			break;
1739		}
1740		case MSG_SET_TERMINAL_TITLE:
1741		{
1742			const char* title;
1743			if (msg->FindString("title", &title) == B_OK) {
1744				if (fListener != NULL)
1745					fListener->SetTermViewTitle(this, title);
1746			}
1747			break;
1748		}
1749		case MSG_SET_TERMINAL_COLORS:
1750		{
1751			int32 count  = 0;
1752			if (msg->FindInt32("count", &count) != B_OK)
1753				break;
1754			bool dynamic  = false;
1755			if (msg->FindBool("dynamic", &dynamic) != B_OK)
1756				break;
1757			for (int i = 0; i < count; i++) {
1758				uint8 index = 0;
1759				if (msg->FindUInt8("index", i, &index) != B_OK)
1760					break;
1761
1762				ssize_t bytes = 0;
1763				rgb_color* color = 0;
1764				if (msg->FindData("color", B_RGB_COLOR_TYPE,
1765							i, (const void**)&color, &bytes) != B_OK)
1766					break;
1767				SetTermColor(index, *color, dynamic);
1768			}
1769			break;
1770		}
1771		case MSG_RESET_TERMINAL_COLORS:
1772		{
1773			int32 count  = 0;
1774			if (msg->FindInt32("count", &count) != B_OK)
1775				break;
1776			bool dynamic  = false;
1777			if (msg->FindBool("dynamic", &dynamic) != B_OK)
1778				break;
1779			for (int i = 0; i < count; i++) {
1780				uint8 index = 0;
1781				if (msg->FindUInt8("index", i, &index) != B_OK)
1782					break;
1783
1784				SetTermColor(index,
1785					TermApp::DefaultPalette()[index], dynamic);
1786			}
1787			break;
1788		}
1789		case MSG_SET_CURSOR_STYLE:
1790		{
1791			int32 style = BLOCK_CURSOR;
1792			if (msg->FindInt32("style", &style) == B_OK)
1793				fCursorStyle = style;
1794
1795			bool blinking = fCursorBlinking;
1796			if (msg->FindBool("blinking", &blinking) == B_OK)
1797				SwitchCursorBlinking(blinking);
1798
1799			bool hidden = fCursorHidden;
1800			if (msg->FindBool("hidden", &hidden) == B_OK)
1801				fCursorHidden = hidden;
1802			break;
1803		}
1804		case MSG_REPORT_MOUSE_EVENT:
1805		{
1806			bool report;
1807			if (msg->FindBool("reportX10MouseEvent", &report) == B_OK)
1808				fReportX10MouseEvent = report;
1809
1810			if (msg->FindBool("reportNormalMouseEvent", &report) == B_OK)
1811				fReportNormalMouseEvent = report;
1812
1813			if (msg->FindBool("reportButtonMouseEvent", &report) == B_OK)
1814				fReportButtonMouseEvent = report;
1815
1816			if (msg->FindBool("reportAnyMouseEvent", &report) == B_OK)
1817				fReportAnyMouseEvent = report;
1818			break;
1819		}
1820		case MSG_REMOVE_RESIZE_VIEW_IF_NEEDED:
1821		{
1822			BPoint point;
1823			uint32 buttons;
1824			GetMouse(&point, &buttons, false);
1825			if (buttons != 0)
1826				break;
1827
1828			if (fResizeView != NULL) {
1829				fResizeView->RemoveSelf();
1830				delete fResizeView;
1831				fResizeView = NULL;
1832			}
1833			delete fResizeRunner;
1834			fResizeRunner = NULL;
1835			break;
1836		}
1837
1838		case MSG_QUIT_TERMNAL:
1839		{
1840			int32 reason;
1841			if (msg->FindInt32("reason", &reason) != B_OK)
1842				reason = 0;
1843			if (fListener != NULL)
1844				fListener->NotifyTermViewQuit(this, reason);
1845			break;
1846		}
1847		default:
1848			BView::MessageReceived(msg);
1849			break;
1850	}
1851}
1852
1853
1854status_t
1855TermView::GetSupportedSuites(BMessage *message)
1856{
1857	BPropertyInfo propInfo(sPropList);
1858	message->AddString("suites", "suite/vnd.naan-termview");
1859	message->AddFlat("messages", &propInfo);
1860	return BView::GetSupportedSuites(message);
1861}
1862
1863
1864void
1865TermView::ScrollTo(BPoint where)
1866{
1867//debug_printf("TermView::ScrollTo(): %f -> %f\n", fScrollOffset, where.y);
1868	float diff = where.y - fScrollOffset;
1869	if (diff == 0)
1870		return;
1871
1872	float bottom = Bounds().bottom;
1873	int32 oldFirstLine = _LineAt(0);
1874	int32 oldLastLine = _LineAt(bottom);
1875	int32 newFirstLine = _LineAt(diff);
1876	int32 newLastLine = _LineAt(bottom + diff);
1877
1878	fScrollOffset = where.y;
1879
1880	// invalidate the current cursor position before scrolling
1881	_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
1882
1883	// scroll contents
1884	BRect destRect(Frame().OffsetToCopy(Bounds().LeftTop()));
1885	BRect sourceRect(destRect.OffsetByCopy(0, diff));
1886//debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
1887//sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
1888//destRect.left, destRect.top, destRect.right, destRect.bottom);
1889	CopyBits(sourceRect, destRect);
1890
1891	// sync visible text buffer with text buffer
1892	if (newFirstLine != oldFirstLine || newLastLine != oldLastLine) {
1893		if (newFirstLine != oldFirstLine)
1894{
1895//debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine);
1896			fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine);
1897}
1898		TextBufferSyncLocker _(this);
1899		if (diff < 0)
1900			_SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1);
1901		else
1902			_SynchronizeWithTextBuffer(oldLastLine + 1, newLastLine);
1903	}
1904}
1905
1906
1907void
1908TermView::TargetedByScrollView(BScrollView *scrollView)
1909{
1910	BView::TargetedByScrollView(scrollView);
1911
1912	SetScrollBar(scrollView ? scrollView->ScrollBar(B_VERTICAL) : NULL);
1913}
1914
1915
1916BHandler*
1917TermView::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
1918	int32 what, const char* property)
1919{
1920	BHandler* target = this;
1921	BPropertyInfo propInfo(sPropList);
1922	if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) {
1923		target = BView::ResolveSpecifier(message, index, specifier, what,
1924			property);
1925	}
1926
1927	return target;
1928}
1929
1930
1931void
1932TermView::_SecondaryMouseButtonDropped(BMessage* msg)
1933{
1934	// Launch menu to choose what is to do with the msg data
1935	BPoint point;
1936	if (msg->FindPoint("_drop_point_", &point) != B_OK)
1937		return;
1938
1939	BMessage* insertMessage = new BMessage(*msg);
1940	insertMessage->what = kSecondaryMouseDropAction;
1941	insertMessage->AddInt8("action", kInsert);
1942
1943	BMessage* cdMessage = new BMessage(*msg);
1944	cdMessage->what = kSecondaryMouseDropAction;
1945	cdMessage->AddInt8("action", kChangeDirectory);
1946
1947	BMessage* lnMessage = new BMessage(*msg);
1948	lnMessage->what = kSecondaryMouseDropAction;
1949	lnMessage->AddInt8("action", kLinkFiles);
1950
1951	BMessage* mvMessage = new BMessage(*msg);
1952	mvMessage->what = kSecondaryMouseDropAction;
1953	mvMessage->AddInt8("action", kMoveFiles);
1954
1955	BMessage* cpMessage = new BMessage(*msg);
1956	cpMessage->what = kSecondaryMouseDropAction;
1957	cpMessage->AddInt8("action", kCopyFiles);
1958
1959	BMenuItem* insertItem = new BMenuItem(
1960		B_TRANSLATE("Insert path"), insertMessage);
1961	BMenuItem* cdItem = new BMenuItem(
1962		B_TRANSLATE("Change directory"), cdMessage);
1963	BMenuItem* lnItem = new BMenuItem(
1964		B_TRANSLATE("Create link here"), lnMessage);
1965	BMenuItem* mvItem = new BMenuItem(B_TRANSLATE("Move here"), mvMessage);
1966	BMenuItem* cpItem = new BMenuItem(B_TRANSLATE("Copy here"), cpMessage);
1967	BMenuItem* chItem = new BMenuItem(B_TRANSLATE("Cancel"), NULL);
1968
1969	// if the refs point to different directorys disable the cd menu item
1970	bool differentDirs = false;
1971	BDirectory firstDir;
1972	entry_ref ref;
1973	int i = 0;
1974	while (msg->FindRef("refs", i++, &ref) == B_OK) {
1975		BNode node(&ref);
1976		BEntry entry(&ref);
1977		BDirectory dir;
1978		if (node.IsDirectory())
1979			dir.SetTo(&ref);
1980		else
1981			entry.GetParent(&dir);
1982
1983		if (i == 1) {
1984			node_ref nodeRef;
1985			dir.GetNodeRef(&nodeRef);
1986			firstDir.SetTo(&nodeRef);
1987		} else if (firstDir != dir) {
1988			differentDirs = true;
1989			break;
1990		}
1991	}
1992	if (differentDirs)
1993		cdItem->SetEnabled(false);
1994
1995	BPopUpMenu *menu = new BPopUpMenu(
1996		"Secondary mouse button drop menu");
1997	menu->SetAsyncAutoDestruct(true);
1998	menu->AddItem(insertItem);
1999	menu->AddSeparatorItem();
2000	menu->AddItem(cdItem);
2001	menu->AddItem(lnItem);
2002	menu->AddItem(mvItem);
2003	menu->AddItem(cpItem);
2004	menu->AddSeparatorItem();
2005	menu->AddItem(chItem);
2006	menu->SetTargetForItems(this);
2007	menu->Go(point, true, true, true);
2008}
2009
2010
2011void
2012TermView::_DoSecondaryMouseDropAction(BMessage* msg)
2013{
2014	int8 action = -1;
2015	msg->FindInt8("action", &action);
2016
2017	BString outString = "";
2018	BString itemString = "";
2019
2020	switch (action) {
2021		case kInsert:
2022			break;
2023		case kChangeDirectory:
2024			outString = "cd ";
2025			break;
2026		case kLinkFiles:
2027			outString = "ln -s ";
2028			break;
2029		case kMoveFiles:
2030			outString = "mv ";
2031			break;
2032		case kCopyFiles:
2033			outString = "cp ";
2034			break;
2035
2036		default:
2037			return;
2038	}
2039
2040	bool listContainsDirectory = false;
2041	entry_ref ref;
2042	int32 i = 0;
2043	while (msg->FindRef("refs", i++, &ref) == B_OK) {
2044		BEntry ent(&ref);
2045		BNode node(&ref);
2046		BPath path(&ent);
2047		BString string(path.Path());
2048
2049		if (node.IsDirectory())
2050			listContainsDirectory = true;
2051
2052		if (i > 1)
2053			itemString += " ";
2054
2055		if (action == kChangeDirectory) {
2056			if (!node.IsDirectory()) {
2057				int32 slash = string.FindLast("/");
2058				string.Truncate(slash);
2059			}
2060			string.CharacterEscape(kShellEscapeCharacters, '\\');
2061			itemString += string;
2062			break;
2063		}
2064		string.CharacterEscape(kShellEscapeCharacters, '\\');
2065		itemString += string;
2066	}
2067
2068	if (listContainsDirectory && action == kCopyFiles)
2069		outString += "-R ";
2070
2071	outString += itemString;
2072
2073	if (action == kLinkFiles || action == kMoveFiles || action == kCopyFiles)
2074		outString += " .";
2075
2076	if (action != kInsert)
2077		outString += "\n";
2078
2079	_WritePTY(outString.String(), outString.Length());
2080}
2081
2082
2083//! Gets dropped file full path and display it at cursor position.
2084void
2085TermView::_DoFileDrop(entry_ref& ref)
2086{
2087	BEntry ent(&ref);
2088	BPath path(&ent);
2089	BString string(path.Path());
2090
2091	string.CharacterEscape(kShellEscapeCharacters, '\\');
2092	_WritePTY(string.String(), string.Length());
2093}
2094
2095
2096/*!	Text buffer must already be locked.
2097*/
2098void
2099TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
2100	int32 visibleDirtyBottom)
2101{
2102	TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo();
2103	int32 linesScrolled = info.linesScrolled;
2104
2105//debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, "
2106//"scrolled: %ld, visible dirty: %ld - %ld\n", info.dirtyTop, info.dirtyBottom,
2107//info.linesScrolled, visibleDirtyTop, visibleDirtyBottom);
2108
2109	bigtime_t now = system_time();
2110	bigtime_t timeElapsed = now - fLastSyncTime;
2111	if (timeElapsed > 2 * kSyncUpdateGranularity) {
2112		// last sync was ages ago
2113		fLastSyncTime = now;
2114		fScrolledSinceLastSync = linesScrolled;
2115	}
2116
2117	if (fSyncRunner == NULL) {
2118		// We consider clocked syncing when more than a full screen height has
2119		// been scrolled in less than a sync update period. Once we're
2120		// actively considering it, the same condition will convince us to
2121		// actually do it.
2122		if (fScrolledSinceLastSync + linesScrolled <= fRows) {
2123			// Condition doesn't hold yet. Reset if time is up, or otherwise
2124			// keep counting.
2125			if (timeElapsed > kSyncUpdateGranularity) {
2126				fConsiderClockedSync = false;
2127				fLastSyncTime = now;
2128				fScrolledSinceLastSync = linesScrolled;
2129			} else
2130				fScrolledSinceLastSync += linesScrolled;
2131		} else if (fConsiderClockedSync) {
2132			// We are convinced -- create the sync runner.
2133			fLastSyncTime = now;
2134			fScrolledSinceLastSync = 0;
2135
2136			BMessage message(MSG_TERMINAL_BUFFER_CHANGED);
2137			fSyncRunner = new(std::nothrow) BMessageRunner(BMessenger(this),
2138				&message, kSyncUpdateGranularity);
2139			if (fSyncRunner != NULL && fSyncRunner->InitCheck() == B_OK)
2140				return;
2141
2142			delete fSyncRunner;
2143			fSyncRunner = NULL;
2144		} else {
2145			// Looks interesting so far. Reset the counts and consider clocked
2146			// syncing.
2147			fConsiderClockedSync = true;
2148			fLastSyncTime = now;
2149			fScrolledSinceLastSync = 0;
2150		}
2151	} else if (timeElapsed < kSyncUpdateGranularity) {
2152		// sync time not passed yet -- keep counting
2153		fScrolledSinceLastSync += linesScrolled;
2154		return;
2155	}
2156
2157	if (fScrolledSinceLastSync + linesScrolled <= fRows) {
2158		// time's up, but not enough happened
2159		delete fSyncRunner;
2160		fSyncRunner = NULL;
2161		fLastSyncTime = now;
2162		fScrolledSinceLastSync = linesScrolled;
2163	} else {
2164		// Things are still rolling, but the sync time's up.
2165		fLastSyncTime = now;
2166		fScrolledSinceLastSync = 0;
2167	}
2168
2169	fVisibleTextBufferChanged = true;
2170
2171	// Simple case first -- complete invalidation.
2172	if (info.invalidateAll) {
2173		Invalidate();
2174		_UpdateScrollBarRange();
2175		_Deselect();
2176
2177		fCursor = fTextBuffer->Cursor();
2178		_ActivateCursor(false);
2179
2180		int32 offset = _LineAt(0);
2181		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset,
2182			offset + fTextBuffer->Height() + 2);
2183
2184		info.Reset();
2185		return;
2186	}
2187
2188	BRect bounds = Bounds();
2189	int32 firstVisible = _LineAt(0);
2190	int32 lastVisible = _LineAt(bounds.bottom);
2191	int32 historySize = fTextBuffer->HistorySize();
2192
2193	bool doScroll = false;
2194	if (linesScrolled > 0) {
2195		_UpdateScrollBarRange();
2196
2197		visibleDirtyTop -= linesScrolled;
2198		visibleDirtyBottom -= linesScrolled;
2199
2200		if (firstVisible < 0) {
2201			firstVisible -= linesScrolled;
2202			lastVisible -= linesScrolled;
2203
2204			float scrollOffset;
2205			if (firstVisible < -historySize) {
2206				firstVisible = -historySize;
2207				doScroll = true;
2208				scrollOffset = -historySize * fFontHeight;
2209				// We need to invalidate the lower linesScrolled lines of the
2210				// visible text buffer, since those will be scrolled up and
2211				// need to be replaced. We just use visibleDirty{Top,Bottom}
2212				// for that purpose. Unless invoked from ScrollTo() (i.e.
2213				// user-initiated scrolling) those are unused. In the unlikely
2214				// case that the user is scrolling at the same time we may
2215				// invalidate too many lines, since we have to extend the given
2216				// region.
2217				// Note that in the firstVisible == 0 case the new lines are
2218				// already in the dirty region, so they will be updated anyway.
2219				if (visibleDirtyTop <= visibleDirtyBottom) {
2220					if (lastVisible < visibleDirtyTop)
2221						visibleDirtyTop = lastVisible;
2222					if (visibleDirtyBottom < lastVisible + linesScrolled)
2223						visibleDirtyBottom = lastVisible + linesScrolled;
2224				} else {
2225					visibleDirtyTop = lastVisible + 1;
2226					visibleDirtyBottom = lastVisible + linesScrolled;
2227				}
2228			} else
2229				scrollOffset = fScrollOffset - linesScrolled * fFontHeight;
2230
2231			_ScrollTo(scrollOffset, false);
2232		} else
2233			doScroll = true;
2234
2235		if (doScroll && lastVisible >= firstVisible
2236			&& !(info.IsDirtyRegionValid() && firstVisible >= info.dirtyTop
2237				&& lastVisible <= info.dirtyBottom)) {
2238			// scroll manually
2239			float scrollBy = linesScrolled * fFontHeight;
2240			BRect destRect(Frame().OffsetToCopy(B_ORIGIN));
2241			BRect sourceRect(destRect.OffsetByCopy(0, scrollBy));
2242
2243			// invalidate the current cursor position before scrolling
2244			_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2245
2246//debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
2247//sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
2248//destRect.left, destRect.top, destRect.right, destRect.bottom);
2249			CopyBits(sourceRect, destRect);
2250
2251			fVisibleTextBuffer->ScrollBy(linesScrolled);
2252		}
2253
2254		// move highlights
2255		for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) {
2256			if (highlight->IsEmpty())
2257				continue;
2258
2259			highlight->ScrollRange(linesScrolled);
2260			if (highlight == &fSelection) {
2261				fInitialSelectionStart.y -= linesScrolled;
2262				fInitialSelectionEnd.y -= linesScrolled;
2263			}
2264
2265			if (highlight->Start().y < -historySize) {
2266				if (highlight == &fSelection)
2267					_Deselect();
2268				else
2269					_ClearHighlight(highlight);
2270			}
2271		}
2272	}
2273
2274	// invalidate dirty region
2275	if (info.IsDirtyRegionValid()) {
2276		_InvalidateTextRect(0, info.dirtyTop, fTextBuffer->Width() - 1,
2277			info.dirtyBottom);
2278
2279		// clear the selection, if affected
2280		if (!fSelection.IsEmpty()) {
2281			// TODO: We're clearing the selection more often than necessary --
2282			// to avoid that, we'd also need to track the x coordinates of the
2283			// dirty range.
2284			int32 selectionBottom = fSelection.End().x > 0
2285				? fSelection.End().y : fSelection.End().y - 1;
2286			if (fSelection.Start().y <= info.dirtyBottom
2287				&& info.dirtyTop <= selectionBottom) {
2288				_Deselect();
2289			}
2290		}
2291	}
2292
2293	if (visibleDirtyTop <= visibleDirtyBottom)
2294		info.ExtendDirtyRegion(visibleDirtyTop, visibleDirtyBottom);
2295
2296	if (linesScrolled != 0 || info.IsDirtyRegionValid()) {
2297		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, firstVisible,
2298			info.dirtyTop, info.dirtyBottom);
2299	}
2300
2301	// invalidate cursor, if it changed
2302	TermPos cursor = fTextBuffer->Cursor();
2303	if (fCursor != cursor || linesScrolled != 0) {
2304		// Before we scrolled we did already invalidate the old cursor.
2305		if (!doScroll)
2306			_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2307		fCursor = cursor;
2308		_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2309		_ActivateCursor(false);
2310	}
2311
2312	info.Reset();
2313}
2314
2315
2316void
2317TermView::_VisibleTextBufferChanged()
2318{
2319	if (!fVisibleTextBufferChanged)
2320		return;
2321
2322	fVisibleTextBufferChanged = false;
2323	fActiveState->VisibleTextBufferChanged();
2324}
2325
2326
2327/*!	Write strings to PTY device. If encoding system isn't UTF8, change
2328	encoding to UTF8 before writing PTY.
2329*/
2330void
2331TermView::_WritePTY(const char* text, int32 numBytes)
2332{
2333	if (fEncoding != M_UTF8) {
2334		while (numBytes > 0) {
2335			char buffer[1024];
2336			int32 bufferSize = sizeof(buffer);
2337			int32 sourceSize = numBytes;
2338			int32 state = 0;
2339			if (convert_to_utf8(fEncoding, text, &sourceSize, buffer,
2340					&bufferSize, &state) != B_OK || bufferSize == 0) {
2341				break;
2342			}
2343
2344			fShell->Write(buffer, bufferSize);
2345			text += sourceSize;
2346			numBytes -= sourceSize;
2347		}
2348	} else {
2349		fShell->Write(text, numBytes);
2350	}
2351}
2352
2353
2354//! Returns the square of the actual pixel distance between both points
2355float
2356TermView::_MouseDistanceSinceLastClick(BPoint where)
2357{
2358	return (fLastClickPoint.x - where.x) * (fLastClickPoint.x - where.x)
2359		+ (fLastClickPoint.y - where.y) * (fLastClickPoint.y - where.y);
2360}
2361
2362
2363void
2364TermView::_SendMouseEvent(int32 buttons, int32 mode, int32 x, int32 y,
2365	bool motion)
2366{
2367	char xtermButtons;
2368	if (buttons == B_PRIMARY_MOUSE_BUTTON)
2369		xtermButtons = 32 + 0;
2370	else if (buttons == B_SECONDARY_MOUSE_BUTTON)
2371		xtermButtons = 32 + 1;
2372	else if (buttons == B_TERTIARY_MOUSE_BUTTON)
2373		xtermButtons = 32 + 2;
2374	else
2375		xtermButtons = 32 + 3;
2376
2377	if (motion)
2378		xtermButtons += 32;
2379
2380	char xtermX = x + 1 + 32;
2381	char xtermY = y + 1 + 32;
2382
2383	char destBuffer[6];
2384	destBuffer[0] = '\033';
2385	destBuffer[1] = '[';
2386	destBuffer[2] = 'M';
2387	destBuffer[3] = xtermButtons;
2388	destBuffer[4] = xtermX;
2389	destBuffer[5] = xtermY;
2390	fShell->Write(destBuffer, 6);
2391}
2392
2393
2394void
2395TermView::MouseDown(BPoint where)
2396{
2397	if (!IsFocus())
2398		MakeFocus();
2399
2400	_UpdateModifiers();
2401
2402	BMessage* currentMessage = Window()->CurrentMessage();
2403	int32 buttons = currentMessage->GetInt32("buttons", 0);
2404
2405	fActiveState->MouseDown(where, buttons, fModifiers);
2406
2407	fMouseButtons = buttons;
2408	fLastClickPoint = where;
2409}
2410
2411
2412void
2413TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message)
2414{
2415	_UpdateModifiers();
2416
2417	fActiveState->MouseMoved(where, transit, message, fModifiers);
2418}
2419
2420
2421void
2422TermView::MouseUp(BPoint where)
2423{
2424	_UpdateModifiers();
2425
2426	int32 buttons = Window()->CurrentMessage()->GetInt32("buttons", 0);
2427
2428	fActiveState->MouseUp(where, buttons);
2429
2430	fMouseButtons = buttons;
2431}
2432
2433
2434//! Select a range of text.
2435void
2436TermView::_Select(TermPos start, TermPos end, bool inclusive,
2437	bool setInitialSelection)
2438{
2439	TextBufferSyncLocker _(this);
2440
2441	_SynchronizeWithTextBuffer(0, -1);
2442
2443	if (end < start)
2444		std::swap(start, end);
2445
2446	if (inclusive)
2447		end.x++;
2448
2449//debug_printf("TermView::_Select(): (%ld, %ld) - (%ld, %ld)\n", start.x,
2450//start.y, end.x, end.y);
2451
2452	if (start.x < 0)
2453		start.x = 0;
2454	if (end.x >= fColumns)
2455		end.x = fColumns;
2456
2457	TermPos minPos(0, -fTextBuffer->HistorySize());
2458	TermPos maxPos(0, fTextBuffer->Height());
2459	start = restrict_value(start, minPos, maxPos);
2460	end = restrict_value(end, minPos, maxPos);
2461
2462	// if the end is past the end of the line, select the line break, too
2463	if (fTextBuffer->LineLength(end.y) < end.x
2464			&& end.y < fTextBuffer->Height()) {
2465		end.y++;
2466		end.x = 0;
2467	}
2468
2469	if (fTextBuffer->IsFullWidthChar(start.y, start.x)) {
2470		start.x--;
2471		if (start.x < 0)
2472			start.x = 0;
2473	}
2474
2475	if (fTextBuffer->IsFullWidthChar(end.y, end.x)) {
2476		end.x++;
2477		if (end.x >= fColumns)
2478			end.x = fColumns;
2479	}
2480
2481	if (!fSelection.IsEmpty())
2482		_InvalidateTextRange(fSelection.Start(), fSelection.End());
2483
2484	fSelection.SetRange(start, end);
2485
2486	if (setInitialSelection) {
2487		fInitialSelectionStart = fSelection.Start();
2488		fInitialSelectionEnd = fSelection.End();
2489	}
2490
2491	_InvalidateTextRange(fSelection.Start(), fSelection.End());
2492}
2493
2494
2495//! Extend selection (shift + mouse click).
2496void
2497TermView::_ExtendSelection(TermPos pos, bool inclusive,
2498	bool useInitialSelection)
2499{
2500	if (!useInitialSelection && !_HasSelection())
2501		return;
2502
2503	TermPos start = fSelection.Start();
2504	TermPos end = fSelection.End();
2505
2506	if (useInitialSelection) {
2507		start = fInitialSelectionStart;
2508		end = fInitialSelectionEnd;
2509	}
2510
2511	if (inclusive) {
2512		if (pos >= start && pos >= end)
2513			pos.x++;
2514	}
2515
2516	if (pos < start)
2517		_Select(pos, end, false, !useInitialSelection);
2518	else if (pos > end)
2519		_Select(start, pos, false, !useInitialSelection);
2520	else if (useInitialSelection)
2521		_Select(start, end, false, false);
2522}
2523
2524
2525// clear the selection.
2526void
2527TermView::_Deselect()
2528{
2529//debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection());
2530	if (_ClearHighlight(&fSelection)) {
2531		fInitialSelectionStart.SetTo(0, 0);
2532		fInitialSelectionEnd.SetTo(0, 0);
2533	}
2534}
2535
2536
2537bool
2538TermView::_HasSelection() const
2539{
2540	return !fSelection.IsEmpty();
2541}
2542
2543
2544void
2545TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection)
2546{
2547	BAutolock _(fTextBuffer);
2548
2549	TermPos pos = _ConvertToTerminal(where);
2550	TermPos start, end;
2551	if (!fTextBuffer->FindWord(pos, fCharClassifier, true, start, end))
2552		return;
2553
2554	if (extend) {
2555		if (start
2556				< (useInitialSelection
2557					? fInitialSelectionStart : fSelection.Start())) {
2558			_ExtendSelection(start, false, useInitialSelection);
2559		} else if (end
2560				> (useInitialSelection
2561					? fInitialSelectionEnd : fSelection.End())) {
2562			_ExtendSelection(end, false, useInitialSelection);
2563		} else if (useInitialSelection)
2564			_Select(start, end, false, false);
2565	} else
2566		_Select(start, end, false, !useInitialSelection);
2567}
2568
2569
2570void
2571TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection)
2572{
2573	TermPos start = TermPos(0, _ConvertToTerminal(where).y);
2574	TermPos end = TermPos(0, start.y + 1);
2575
2576	if (extend) {
2577		if (start
2578				< (useInitialSelection
2579					? fInitialSelectionStart : fSelection.Start())) {
2580			_ExtendSelection(start, false, useInitialSelection);
2581		} else if (end
2582				> (useInitialSelection
2583					? fInitialSelectionEnd : fSelection.End())) {
2584			_ExtendSelection(end, false, useInitialSelection);
2585		} else if (useInitialSelection)
2586			_Select(start, end, false, false);
2587	} else
2588		_Select(start, end, false, !useInitialSelection);
2589}
2590
2591
2592void
2593TermView::_AddHighlight(Highlight* highlight)
2594{
2595	fHighlights.AddItem(highlight);
2596
2597	if (!highlight->IsEmpty())
2598		_InvalidateTextRange(highlight->Start(), highlight->End());
2599}
2600
2601
2602void
2603TermView::_RemoveHighlight(Highlight* highlight)
2604{
2605	if (!highlight->IsEmpty())
2606		_InvalidateTextRange(highlight->Start(), highlight->End());
2607
2608	fHighlights.RemoveItem(highlight);
2609}
2610
2611
2612bool
2613TermView::_ClearHighlight(Highlight* highlight)
2614{
2615	if (highlight->IsEmpty())
2616		return false;
2617
2618	_InvalidateTextRange(highlight->Start(), highlight->End());
2619
2620	highlight->SetRange(TermPos(0, 0), TermPos(0, 0));
2621	return true;
2622}
2623
2624
2625TermView::Highlight*
2626TermView::_CheckHighlightRegion(const TermPos &pos) const
2627{
2628	for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) {
2629		if (highlight->RangeContains(pos))
2630			return highlight;
2631	}
2632
2633	return NULL;
2634}
2635
2636
2637TermView::Highlight*
2638TermView::_CheckHighlightRegion(int32 row, int32 firstColumn,
2639	int32& lastColumn) const
2640{
2641	Highlight* nextHighlight = NULL;
2642
2643	for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) {
2644		if (highlight->IsEmpty())
2645			continue;
2646
2647		if (row == highlight->Start().y && firstColumn < highlight->Start().x
2648				&& lastColumn >= highlight->Start().x) {
2649			// region starts before the highlight, but intersects with it
2650			if (nextHighlight == NULL
2651				|| highlight->Start().x < nextHighlight->Start().x) {
2652				nextHighlight = highlight;
2653			}
2654			continue;
2655		}
2656
2657		if (row == highlight->End().y && firstColumn < highlight->End().x
2658				&& lastColumn >= highlight->End().x) {
2659			// region starts in the highlight, but exceeds the end
2660			lastColumn = highlight->End().x - 1;
2661			return highlight;
2662		}
2663
2664		TermPos pos(firstColumn, row);
2665		if (highlight->RangeContains(pos))
2666			return highlight;
2667	}
2668
2669	if (nextHighlight != NULL)
2670		lastColumn = nextHighlight->Start().x - 1;
2671	return NULL;
2672}
2673
2674
2675void
2676TermView::GetFrameSize(float *width, float *height)
2677{
2678	int32 historySize;
2679	{
2680		BAutolock _(fTextBuffer);
2681		historySize = fTextBuffer->HistorySize();
2682	}
2683
2684	if (width != NULL)
2685		*width = fColumns * fFontWidth;
2686
2687	if (height != NULL)
2688		*height = (fRows + historySize) * fFontHeight;
2689}
2690
2691
2692// Find a string, and select it if found
2693bool
2694TermView::Find(const BString &str, bool forwardSearch, bool matchCase,
2695	bool matchWord)
2696{
2697	TextBufferSyncLocker _(this);
2698	_SynchronizeWithTextBuffer(0, -1);
2699
2700	TermPos start;
2701	if (_HasSelection()) {
2702		if (forwardSearch)
2703			start = fSelection.End();
2704		else
2705			start = fSelection.Start();
2706	} else {
2707		// search from the very beginning/end
2708		if (forwardSearch)
2709			start = TermPos(0, -fTextBuffer->HistorySize());
2710		else
2711			start = TermPos(0, fTextBuffer->Height());
2712	}
2713
2714	TermPos matchStart, matchEnd;
2715	if (!fTextBuffer->Find(str.String(), start, forwardSearch, matchCase,
2716			matchWord, matchStart, matchEnd)) {
2717		return false;
2718	}
2719
2720	_Select(matchStart, matchEnd, false, true);
2721	_ScrollToRange(fSelection.Start(), fSelection.End());
2722
2723	return true;
2724}
2725
2726
2727//! Get the selected text and copy to str
2728void
2729TermView::GetSelection(BString &str)
2730{
2731	str.SetTo("");
2732	BAutolock _(fTextBuffer);
2733	fTextBuffer->GetStringFromRegion(str, fSelection.Start(), fSelection.End());
2734}
2735
2736
2737bool
2738TermView::CheckShellGone() const
2739{
2740	if (!fShell)
2741		return false;
2742
2743	// check, if the shell does still live
2744	pid_t pid = fShell->ProcessID();
2745	team_info info;
2746	return get_team_info(pid, &info) == B_BAD_TEAM_ID;
2747}
2748
2749
2750void
2751TermView::InitiateDrag()
2752{
2753	BAutolock _(fTextBuffer);
2754
2755	BString copyStr("");
2756	fTextBuffer->GetStringFromRegion(copyStr, fSelection.Start(),
2757		fSelection.End());
2758
2759	BMessage message(B_MIME_DATA);
2760	message.AddData("text/plain", B_MIME_TYPE, copyStr.String(),
2761		copyStr.Length());
2762
2763	BPoint start = _ConvertFromTerminal(fSelection.Start());
2764	BPoint end = _ConvertFromTerminal(fSelection.End());
2765
2766	BRect rect;
2767	if (fSelection.Start().y == fSelection.End().y)
2768		rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight);
2769	else
2770		rect.Set(0, start.y, fColumns * fFontWidth, end.y + fFontHeight);
2771
2772	rect = rect & Bounds();
2773
2774	DragMessage(&message, rect);
2775}
2776
2777
2778void
2779TermView::_ScrollTo(float y, bool scrollGfx)
2780{
2781	if (!scrollGfx)
2782		fScrollOffset = y;
2783
2784	if (fScrollBar != NULL)
2785		fScrollBar->SetValue(y);
2786	else
2787		ScrollTo(BPoint(0, y));
2788}
2789
2790
2791void
2792TermView::_ScrollToRange(TermPos start, TermPos end)
2793{
2794	if (start > end)
2795		std::swap(start, end);
2796
2797	float startY = _LineOffset(start.y);
2798	float endY = _LineOffset(end.y) + fFontHeight - 1;
2799	float height = Bounds().Height();
2800
2801	if (endY - startY > height) {
2802		// The range is greater than the height. Scroll to the closest border.
2803
2804		// already as good as it gets?
2805		if (startY <= 0 && endY >= height)
2806			return;
2807
2808		if (startY > 0) {
2809			// scroll down to align the start with the top of the view
2810			_ScrollTo(fScrollOffset + startY, true);
2811		} else {
2812			// scroll up to align the end with the bottom of the view
2813			_ScrollTo(fScrollOffset + endY - height, true);
2814		}
2815	} else {
2816		// The range is smaller than the height.
2817
2818		// already visible?
2819		if (startY >= 0 && endY <= height)
2820			return;
2821
2822		if (startY < 0) {
2823			// scroll up to make the start visible
2824			_ScrollTo(fScrollOffset + startY, true);
2825		} else {
2826			// scroll down to make the end visible
2827			_ScrollTo(fScrollOffset + endY - height, true);
2828		}
2829	}
2830}
2831
2832
2833void
2834TermView::DisableResizeView(int32 disableCount)
2835{
2836	fResizeViewDisableCount += disableCount;
2837}
2838
2839
2840void
2841TermView::_DrawInlineMethodString()
2842{
2843	if (!fInline || !fInline->String())
2844		return;
2845
2846	const int32 numChars = BString(fInline->String()).CountChars();
2847
2848	BPoint startPoint = _ConvertFromTerminal(fCursor);
2849	BPoint endPoint = startPoint;
2850	endPoint.x += fFontWidth * numChars;
2851	endPoint.y += fFontHeight + 1;
2852
2853	BRect eraseRect(startPoint, endPoint);
2854
2855	PushState();
2856	SetHighColor(fTextForeColor);
2857	FillRect(eraseRect);
2858	PopState();
2859
2860	BPoint loc = _ConvertFromTerminal(fCursor);
2861	loc.y += fFontHeight;
2862	SetFont(&fHalfFont);
2863	SetHighColor(fTextBackColor);
2864	SetLowColor(fTextForeColor);
2865	DrawString(fInline->String(), loc);
2866}
2867
2868
2869void
2870TermView::_HandleInputMethodChanged(BMessage *message)
2871{
2872	const char *string = NULL;
2873	if (message->FindString("be:string", &string) < B_OK || string == NULL)
2874		return;
2875
2876	_ActivateCursor(false);
2877
2878	if (IsFocus())
2879		be_app->ObscureCursor();
2880
2881	// If we find the "be:confirmed" boolean (and the boolean is true),
2882	// it means it's over for now, so the current InlineInput object
2883	// should become inactive. We will probably receive a
2884	// B_INPUT_METHOD_STOPPED message after this one.
2885	bool confirmed;
2886	if (message->FindBool("be:confirmed", &confirmed) != B_OK)
2887		confirmed = false;
2888
2889	fInline->SetString("");
2890
2891	Invalidate();
2892	// TODO: Debug only
2893	snooze(100000);
2894
2895	fInline->SetString(string);
2896	fInline->ResetClauses();
2897
2898	if (!confirmed && !fInline->IsActive())
2899		fInline->SetActive(true);
2900
2901	// Get the clauses, and pass them to the InlineInput object
2902	// TODO: Find out if what we did it's ok, currently we don't consider
2903	// clauses at all, while the bebook says we should; though the visual
2904	// effect we obtained seems correct. Weird.
2905	int32 clauseCount = 0;
2906	int32 clauseStart;
2907	int32 clauseEnd;
2908	while (message->FindInt32("be:clause_start", clauseCount, &clauseStart)
2909			== B_OK
2910		&& message->FindInt32("be:clause_end", clauseCount, &clauseEnd)
2911			== B_OK) {
2912		if (!fInline->AddClause(clauseStart, clauseEnd))
2913			break;
2914		clauseCount++;
2915	}
2916
2917	if (confirmed) {
2918		fInline->SetString("");
2919		_ActivateCursor(true);
2920
2921		// now we need to feed ourselves the individual characters as if the
2922		// user would have pressed them now - this lets KeyDown() pick out all
2923		// the special characters like B_BACKSPACE, cursor keys and the like:
2924		const char* currPos = string;
2925		const char* prevPos = currPos;
2926		while (*currPos != '\0') {
2927			if ((*currPos & 0xC0) == 0xC0) {
2928				// found the start of an UTF-8 char, we collect while it lasts
2929				++currPos;
2930				while ((*currPos & 0xC0) == 0x80)
2931					++currPos;
2932			} else if ((*currPos & 0xC0) == 0x80) {
2933				// illegal: character starts with utf-8 intermediate byte, skip it
2934				prevPos = ++currPos;
2935			} else {
2936				// single byte character/code, just feed that
2937				++currPos;
2938			}
2939			KeyDown(prevPos, currPos - prevPos);
2940			prevPos = currPos;
2941		}
2942
2943		Invalidate();
2944	} else {
2945		// temporarily show transient state of inline input
2946		int32 selectionStart = 0;
2947		int32 selectionEnd = 0;
2948		message->FindInt32("be:selection", 0, &selectionStart);
2949		message->FindInt32("be:selection", 1, &selectionEnd);
2950
2951		fInline->SetSelectionOffset(selectionStart);
2952		fInline->SetSelectionLength(selectionEnd - selectionStart);
2953		Invalidate();
2954	}
2955}
2956
2957
2958void
2959TermView::_HandleInputMethodLocationRequest()
2960{
2961	BMessage message(B_INPUT_METHOD_EVENT);
2962	message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
2963
2964	BString string(fInline->String());
2965
2966	const int32 &limit = string.CountChars();
2967	BPoint where = _ConvertFromTerminal(fCursor);
2968	where.y += fFontHeight;
2969
2970	for (int32 i = 0; i < limit; i++) {
2971		// Add the location of the UTF8 characters
2972
2973		where.x += fFontWidth;
2974		ConvertToScreen(&where);
2975
2976		message.AddPoint("be:location_reply", where);
2977		message.AddFloat("be:height_reply", fFontHeight);
2978	}
2979
2980	fInline->Method()->SendMessage(&message);
2981}
2982
2983
2984void
2985TermView::_CancelInputMethod()
2986{
2987	if (!fInline)
2988		return;
2989
2990	InlineInput *inlineInput = fInline;
2991	fInline = NULL;
2992
2993	if (inlineInput->IsActive() && Window()) {
2994		Invalidate();
2995
2996		BMessage message(B_INPUT_METHOD_EVENT);
2997		message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
2998		inlineInput->Method()->SendMessage(&message);
2999	}
3000
3001	delete inlineInput;
3002}
3003
3004
3005void
3006TermView::_UpdateModifiers()
3007{
3008	// TODO: This method is a general work-around for missing or out-of-order
3009	// B_MODIFIERS_CHANGED messages. This should really be fixed where it is
3010	// broken (app server?).
3011	int32 oldModifiers = fModifiers;
3012	fModifiers = modifiers();
3013	if (fModifiers != oldModifiers && fActiveState != NULL)
3014		fActiveState->ModifiersChanged(oldModifiers, fModifiers);
3015}
3016
3017
3018void
3019TermView::_NextState(State* state)
3020{
3021	if (state != fActiveState) {
3022		if (fActiveState != NULL)
3023			fActiveState->Exited();
3024		fActiveState = state;
3025		fActiveState->Entered();
3026	}
3027}
3028
3029
3030// #pragma mark - Listener
3031
3032
3033TermView::Listener::~Listener()
3034{
3035}
3036
3037
3038void
3039TermView::Listener::NotifyTermViewQuit(TermView* view, int32 reason)
3040{
3041}
3042
3043
3044void
3045TermView::Listener::SetTermViewTitle(TermView* view, const char* title)
3046{
3047}
3048
3049
3050void
3051TermView::Listener::PreviousTermView(TermView* view)
3052{
3053}
3054
3055
3056void
3057TermView::Listener::NextTermView(TermView* view)
3058{
3059}
3060
3061
3062// #pragma mark -
3063
3064
3065#ifdef USE_DEBUG_SNAPSHOTS
3066
3067void
3068TermView::MakeDebugSnapshots()
3069{
3070	BAutolock _(fTextBuffer);
3071	time_t timeStamp = time(NULL);
3072	fTextBuffer->MakeLinesSnapshots(timeStamp, ".TextBuffer.dump");
3073	fVisibleTextBuffer->MakeLinesSnapshots(timeStamp, ".VisualTextBuffer.dump");
3074}
3075
3076
3077void
3078TermView::StartStopDebugCapture()
3079{
3080	BAutolock _(fTextBuffer);
3081	fTextBuffer->StartStopDebugCapture();
3082}
3083
3084#endif
3085