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